From 3584e1d71d260a44f9aa685503bdde2d62ad322f Mon Sep 17 00:00:00 2001 From: Christian Holzer Date: Thu, 15 Oct 2015 10:01:55 +0200 Subject: [PATCH] [OLINGO-801] UriParser supports Complex / Entity Parameters. --- .../olingo/server/core/uri/antlr/UriLexer.g4 | 8 +- .../olingo/server/core/uri/antlr/UriParser.g4 | 11 +- .../olingo/server/core/uri/UriInfoImpl.java | 22 ++- .../olingo/server/core/uri/parser/Parser.java | 27 ++- .../server/core/uri/parser/UriContext.java | 7 +- .../core/uri/parser/UriParseTreeVisitor.java | 160 +++++++++++------- .../parser/UriParserSemanticException.java | 6 +- .../uri/parser/UriParserSyntaxException.java | 4 +- .../uri/validator/UriValidationException.java | 7 +- .../core/uri/validator/UriValidator.java | 53 ++++++ .../server-core-exceptions-i18n.properties | 5 + .../core/uri/antlr/TestFullResourcePath.java | 90 +++++++++- .../core/uri/antlr/TestUriParserImpl.java | 2 +- .../uri/testutil/EdmTechTestProvider.java | 13 +- .../core/uri/testutil/FilterValidator.java | 28 ++- 15 files changed, 353 insertions(+), 90 deletions(-) diff --git a/lib/server-core/src/main/antlr4/org/apache/olingo/server/core/uri/antlr/UriLexer.g4 b/lib/server-core/src/main/antlr4/org/apache/olingo/server/core/uri/antlr/UriLexer.g4 index 892accc26..7a54ab182 100644 --- a/lib/server-core/src/main/antlr4/org/apache/olingo/server/core/uri/antlr/UriLexer.g4 +++ b/lib/server-core/src/main/antlr4/org/apache/olingo/server/core/uri/antlr/UriLexer.g4 @@ -29,6 +29,7 @@ STRING : '\'' -> more, pushMode(MODE_STRING) ; QUOTATION_MARK : '\u0022' -> more, pushMode(MODE_JSON_STRING); //reads up to next unescaped " SEARCH_INLINE : '$search' -> pushMode(MODE_SYSTEM_QUERY_SEARCH); // FRAGMENT : '#' -> pushMode(MODE_FRAGMENT); // +STRING_JSON : '"' -> more, pushMode(MODE_JSON_STRING); //reads up to next unescaped " GEOGRAPHY : G E O G R A P H Y SQUOTE -> pushMode(MODE_ODATA_GEO); //TODO make case insensitive GEOMETRY : G E O M E T R Y SQUOTE -> pushMode(MODE_ODATA_GEO); @@ -364,8 +365,11 @@ mode MODE_JSON_STRING; // Any """ characters inside a string are escaped with "\". //;============================================================================== -STRING_IN_JSON : ('\\"' | ~[\u0022] )* '"' -> popMode; -ERROR_CHARACTER_jsm : EOF | .; +STRING_IN_JSON : (ESCAPED_JSON_CHAR | ~["\\])* '"' -> popMode; +fragment ESCAPED_JSON_CHAR : '\\' (["\\/bfnrt] | UNICODE_CHAR); +fragment UNICODE_CHAR : 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT; +fragment HEX_DIGIT : [0-9a-fA-F]; +ERROR_CHARACTER_jsm : EOF | .; //;============================================================================== mode MODE_ODATA_GEO; diff --git a/lib/server-core/src/main/antlr4/org/apache/olingo/server/core/uri/antlr/UriParser.g4 b/lib/server-core/src/main/antlr4/org/apache/olingo/server/core/uri/antlr/UriParser.g4 index 198d1badd..dc1af2b0a 100644 --- a/lib/server-core/src/main/antlr4/org/apache/olingo/server/core/uri/antlr/UriParser.g4 +++ b/lib/server-core/src/main/antlr4/org/apache/olingo/server/core/uri/antlr/UriParser.g4 @@ -205,6 +205,7 @@ commonExpr : OPEN commonExpr CLOSE | rootExpr #altRoot // $... | AT odataIdentifier #altAlias // @... | primitiveLiteral #altLiteral // ... + | arrayOrObject #altJson ; unary : (MINUS| NOT) ; @@ -309,11 +310,15 @@ json_value : jsonPrimitiv | json_array; json_object : BEGIN_OBJECT - STRING_IN_JSON - WSP? COLON WSP? - json_value + ( + json_key_value_pair + (COMMA json_key_value_pair)* + )? END_OBJECT; +json_key_value_pair : STRING_IN_JSON + WSP? COLON WSP? + json_value; //; JSON syntax: adapted to URI restrictions from [RFC4627] jsonPrimitiv : STRING_IN_JSON diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriInfoImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriInfoImpl.java index 6b42a9935..0aff5b6de 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriInfoImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriInfoImpl.java @@ -25,8 +25,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.olingo.commons.api.ex.ODataRuntimeException; import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.ex.ODataRuntimeException; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriInfoAll; import org.apache.olingo.server.api.uri.UriInfoBatch; @@ -36,6 +36,7 @@ import org.apache.olingo.server.api.uri.UriInfoKind; import org.apache.olingo.server.api.uri.UriInfoMetadata; import org.apache.olingo.server.api.uri.UriInfoResource; import org.apache.olingo.server.api.uri.UriInfoService; +import org.apache.olingo.server.api.uri.UriParameter; import org.apache.olingo.server.api.uri.UriResource; import org.apache.olingo.server.api.uri.queryoption.CountOption; import org.apache.olingo.server.api.uri.queryoption.CustomQueryOption; @@ -63,8 +64,8 @@ public class UriInfoImpl implements UriInfo { private EdmEntityType entityTypeCast; // for $entity private List customQueryOptions = new ArrayList(); - private Map aliasToValue = new HashMap(); - + private Map aliasValues = new HashMap(); + private Map systemQueryOptions = new HashMap(); @@ -138,9 +139,18 @@ public class UriInfoImpl implements UriInfo { @Override public String getValueForAlias(final String alias) { - return aliasToValue.get(alias); + final UriParameter parameter = aliasValues.get(alias); + return parameter == null ? null : parameter.getText(); } + public UriParameter getAlias(final String key) { + return aliasValues.get(key); + } + + public void addAlias(final String key, UriParameter parameter) { + aliasValues.put(key, parameter); + } + @Override public EdmEntityType getEntityTypeCast() { return entityTypeCast; @@ -235,9 +245,6 @@ public class UriInfoImpl implements UriInfo { public void addCustomQueryOption(final CustomQueryOptionImpl item) { customQueryOptions.add(item); - if (item.getName().startsWith("@")) { - aliasToValue.put(item.getName(), item.getText()); - } } /** @@ -297,5 +304,4 @@ public class UriInfoImpl implements UriInfo { public Collection getSystemQueryOptions() { return Collections.unmodifiableCollection(systemQueryOptions.values()); } - } \ No newline at end of file diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java index 794948e88..9beb2512e 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java @@ -30,8 +30,8 @@ import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.atn.PredictionMode; import org.antlr.v4.runtime.misc.ParseCancellationException; -import org.apache.olingo.commons.api.ex.ODataRuntimeException; import org.apache.olingo.commons.api.edm.Edm; +import org.apache.olingo.commons.api.ex.ODataRuntimeException; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriInfoKind; import org.apache.olingo.server.api.uri.UriResource; @@ -42,6 +42,7 @@ import org.apache.olingo.server.api.uri.UriResourceValue; 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.UriParameterImpl; 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.AllEOFContext; @@ -65,11 +66,14 @@ import org.apache.olingo.server.core.uri.queryoption.SelectOptionImpl; import org.apache.olingo.server.core.uri.queryoption.SkipOptionImpl; import org.apache.olingo.server.core.uri.queryoption.SkipTokenOptionImpl; import org.apache.olingo.server.core.uri.queryoption.TopOptionImpl; +import org.apache.olingo.server.core.uri.queryoption.expression.ExpressionImpl; public class Parser { private static final String ATOM = "atom"; private static final String JSON = "json"; private static final String XML = "xml"; + private static final String AT = "@"; + private static final String NULL = "null"; int logLevel = 0; private enum ParserEntryRules { @@ -271,7 +275,26 @@ public class Parser { UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, option.name); } } else { - CustomQueryOptionImpl customOption = new CustomQueryOptionImpl(); + if (option.name.startsWith(AT)) { + final FilterExpressionEOFContext filterExpCtx = + (FilterExpressionEOFContext) parseRule(option.value, ParserEntryRules.FilterExpression); + final ExpressionImpl expression = ((FilterOptionImpl) uriParseTreeVisitor + .visitFilterExpressionEOF(filterExpCtx)).getExpression(); + + final UriParameterImpl parameter = new UriParameterImpl(); + parameter.setAlias(option.name); + parameter.setExpression(expression); + parameter.setText(NULL.equals(option.value) ? null : option.value); + + if(context.contextUriInfo.getAlias(option.name) == null) { + context.contextUriInfo.addAlias(option.name, parameter); + } else { + throw new UriParserSyntaxException("Alias already specified! Name: " + option.name, + UriParserSyntaxException.MessageKeys.DUPLICATED_ALIAS, option.name); + } + } + + final CustomQueryOptionImpl customOption = new CustomQueryOptionImpl(); customOption.setName(option.name); customOption.setText(option.value); context.contextUriInfo.addCustomQueryOption(customOption); diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriContext.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriContext.java index a6b9bff5b..b6b6fda10 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriContext.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriContext.java @@ -95,7 +95,12 @@ public class UriContext { */ public UriInfoImpl contextUriInfo; public boolean contextReadingFunctionParameters; - + + /** + * Set to true if the parser operates on query part. + */ + public boolean contextReadingQueryPart; + public UriContext() { contextExpandItemPath = null; diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParseTreeVisitor.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParseTreeVisitor.java index 7ea1adbed..40c67c79b 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParseTreeVisitor.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParseTreeVisitor.java @@ -95,6 +95,7 @@ import org.apache.olingo.server.core.uri.antlr.UriParserParser.AltHasContext; import org.apache.olingo.server.core.uri.antlr.UriParserParser.AltMultContext; import org.apache.olingo.server.core.uri.antlr.UriParserParser.AltOrContext; import org.apache.olingo.server.core.uri.antlr.UriParserParser.AnyExprContext; +import org.apache.olingo.server.core.uri.antlr.UriParserParser.ArrayOrObjectContext; import org.apache.olingo.server.core.uri.antlr.UriParserParser.BatchEOFContext; import org.apache.olingo.server.core.uri.antlr.UriParserParser.BinaryLiteralContext; import org.apache.olingo.server.core.uri.antlr.UriParserParser.BooleanNonCaseLiteralContext; @@ -116,6 +117,7 @@ import org.apache.olingo.server.core.uri.antlr.UriParserParser.EnumLiteralContex import org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandCountOptionContext; import org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandItemContext; import org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandItemsContext; +import org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandItemsEOFContext; import org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandOptionContext; import org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandPathContext; import org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandPathExtensionContext; @@ -179,6 +181,7 @@ import org.apache.olingo.server.core.uri.antlr.UriParserParser.TotalOffsetMinute import org.apache.olingo.server.core.uri.antlr.UriParserParser.TotalsecondsMethodCallExprContext; import org.apache.olingo.server.core.uri.antlr.UriParserParser.TrimMethodCallExprContext; import org.apache.olingo.server.core.uri.antlr.UriParserParser.YearMethodCallExprContext; +import org.apache.olingo.server.core.uri.parser.UriParserSemanticException.MessageKeys; 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; @@ -306,7 +309,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { final boolean checkFirst = context.contextUriInfo.getLastResourcePart() == null - || context.contextUriInfo.getLastResourcePart() instanceof UriResourceRootImpl; + || context.contextUriInfo.getLastResourcePart() instanceof UriResourceRootImpl; String odi = ctx.vODI.getText(); @@ -361,6 +364,12 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { // check FunctionImport EdmFunctionImport edmFunctionImport = edmEntityContainer.getFunctionImport(odi); + + if(edmFunctionImport != null && context.contextReadingQueryPart) { + throw wrap(new UriParserSemanticException("Function Imports are not allowed in $filter or $orderby", + UriParserSemanticException.MessageKeys.FUNCTION_IMPORT_NOT_ALLOWED, odi)); + } + if (edmFunctionImport != null && (parts.isEmpty() || !(parts.get(0) instanceof UriResourcePartTyped) || parts.get(0) instanceof UriResourceRoot)) { @@ -399,7 +408,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { + "' with parameters [" + tmp.toString() + "] not found", UriParserSemanticException.MessageKeys.FUNCTION_NOT_FOUND, edmFunctionImport.getName(), tmp.toString())); } - + ensureNamespaceIsNull(ctx.vNS); uriResource.setFunction(edmFunctionImport.getUnboundFunction(names)); context.contextUriInfo.addResourcePart(uriResource); @@ -466,8 +475,8 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { throw wrap(new UriParserSemanticException("Property '" + odi + "' not found in type '" + structType.getFullQualifiedName().getFullQualifiedNameAsString() + "'", ctx.depth() > 2 ? // path evaluation inside an expression or for the resource path? - UriParserSemanticException.MessageKeys.EXPRESSION_PROPERTY_NOT_IN_TYPE : - UriParserSemanticException.MessageKeys.PROPERTY_NOT_IN_TYPE, + UriParserSemanticException.MessageKeys.EXPRESSION_PROPERTY_NOT_IN_TYPE + : UriParserSemanticException.MessageKeys.PROPERTY_NOT_IN_TYPE, structType.getFullQualifiedName().getFullQualifiedNameAsString(), odi)); } @@ -492,7 +501,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { "Navigation properties in expand system query options must not be followed by a key.", UriParserSemanticException.MessageKeys.KEY_NOT_ALLOWED)); } - + UriResourceNavigationPropertyImpl navigationResource = new UriResourceNavigationPropertyImpl() .setNavigationProperty((EdmNavigationProperty) property); context.contextUriInfo.addResourcePart(navigationResource); @@ -666,8 +675,8 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { // do a check for bound functions (which requires a parameter list) if (ctx.vlNVO.size() == 0) { - throw wrap(new UriParserSemanticException("Unknown type for type cast " + fullFilterName.toString() - + " not found", UriParserSemanticException.MessageKeys.UNKNOWN_TYPE , fullFilterName.toString())); + throw wrap(new UriParserSemanticException("Unknown type for type cast " + fullFilterName.toString() + + " not found", UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, fullFilterName.toString())); } context.contextReadingFunctionParameters = true; @@ -712,17 +721,17 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { UriParserSemanticException.MessageKeys.UNKNOWN_PART, fullFilterName.toString())); } } - + /** * Ensures that the namespace of the first resource parts is null * @param vNS namespace or null */ private void ensureNamespaceIsNull(final NamespaceContext vNS) { - if(vNS != null && context.contextUriInfo.getLastResourcePart() == null) { + if (vNS != null && context.contextUriInfo.getLastResourcePart() == null) { // First resource part and namespace is not null! - throw wrap(new UriParserSemanticException("Namespace is not allowed for EntitySets, Singeltons, " - + " Action Imports and Function Imports. Found " + vNS.getText(), - UriParserSemanticException.MessageKeys.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT, vNS.getText())); + throw wrap(new UriParserSemanticException("Namespace is not allowed for EntitySets, Singeltons, " + + " Action Imports and Function Imports. Found " + vNS.getText(), + UriParserSemanticException.MessageKeys.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT, vNS.getText())); } } @@ -1138,22 +1147,22 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { UriInfoImpl crossJoin = new UriInfoImpl().setKind(UriInfoKind.crossjoin); for (OdataIdentifierContext obj : ctx.vlODI) { - String odi = obj.getText(); + String odi = obj.getText(); crossJoin.addEntitySetName(odi); - + EdmEntitySet edmEntitySet = edmEntityContainer.getEntitySet(odi); if (edmEntitySet == null) { throw wrap(new UriParserSemanticException("Expected EntityTypeName", - UriParserSemanticException.MessageKeys.UNKNOWN_PART, odi)); + UriParserSemanticException.MessageKeys.UNKNOWN_PART, odi)); } - + EdmEntityType type = edmEntitySet.getEntityType(); if (type == null) { throw wrap(new UriParserSemanticException("Expected EntityTypeName", UriParserSemanticException.MessageKeys.UNKNOWN_ENTITY_TYPE, odi)); } // contextUriInfo = uriInfo; - context.contextTypes.push(new TypeInformation(type, true)); + context.contextTypes.push(new TypeInformation(type, true)); } context.contextUriInfo = crossJoin; @@ -1237,10 +1246,10 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { levels.setText(ctx.vM.getText()); try { expandItem.setSystemQueryOption(levels); - } catch(ODataRuntimeException e) { + } catch (ODataRuntimeException e) { // Thrown if duplicated system query options are detected throw wrap(new UriParserSyntaxException("Double system query option!", e, - UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, e.getMessage())); + UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, e.getMessage())); } } else if (ctx.vL != null) { LevelsOptionImpl levels = new LevelsOptionImpl(); @@ -1249,10 +1258,10 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { levels.setValue(Integer.parseInt(text)); try { expandItem.setSystemQueryOption(levels); - } catch(ODataRuntimeException e) { + } catch (ODataRuntimeException e) { // Thrown if duplicated system query options are detected throw wrap(new UriParserSyntaxException("Double system query option!", e, - UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, e.getMessage())); + UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, e.getMessage())); } } @@ -1269,10 +1278,10 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { for (SystemQueryOptionImpl option : list) { expandItem.setSystemQueryOption(option); } - } catch(ODataRuntimeException e) { + } catch (ODataRuntimeException e) { // Thrown if duplicated system query options are detected throw wrap(new UriParserSyntaxException("Double system query option!", e, - UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, e.getMessage())); + UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, e.getMessage())); } context.contextExpandItemPath = contextExpandItemPathBU; } @@ -1293,11 +1302,11 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { // set tmp context context.contextExpandItemPath = expandItem; context.contextUriInfo = new UriInfoImpl().setKind(UriInfoKind.resource); - + context.contextVisitExpandResourcePath = true; super.visitExpandPath(ctx); context.contextVisitExpandResourcePath = false; - + EdmType startType = removeUriResourceStartingTypeFilterImpl(context.contextUriInfo); expandItem.setResourcePath(context.contextUriInfo); if (startType != null) { @@ -1400,14 +1409,22 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { @Override public Object visitFilter(final FilterContext ctx) { + context.contextReadingQueryPart = true; + final FilterOptionImpl result = new FilterOptionImpl().setExpression((ExpressionImpl) ctx.children.get(2) + .accept(this)); + context.contextReadingQueryPart = false; - return new FilterOptionImpl().setExpression((ExpressionImpl) ctx.children.get(2).accept(this)); + return result; } @Override public Object visitFilterExpressionEOF(final FilterExpressionEOFContext ctx) { + context.contextReadingQueryPart = true; + final FilterOptionImpl result = new FilterOptionImpl().setExpression((ExpressionImpl) ctx.children.get(0) + .accept(this)); + context.contextReadingQueryPart = false; - return new FilterOptionImpl().setExpression((ExpressionImpl) ctx.children.get(0).accept(this)); + return result; } @Override @@ -1614,7 +1631,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { UriParserSemanticException.MessageKeys.KEY_NOT_ALLOWED)); } else { // The functions returns a collection of entities - // Get the EDM Type and determine how many key predicates are needed. In this case only one + // Get the EDM Type and determine how many key predicates are needed. In this case only one // key predicate is allowed. If the entity type needs more than one key predicate, the client // has to use the key value syntax e.g. EntitySet(ID=1,Order=2) final EdmEntityType entityType = (EdmEntityType) uriResourceFunction.getFunction().getReturnType().getType(); @@ -1711,7 +1728,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { throw wrap(new UriParserSemanticException("Parameters list on untyped resource path segment not allowed", UriParserSemanticException.MessageKeys.PARAMETERS_LIST_ONLY_FOR_TYPED_PARTS)); } - if(last instanceof UriResourceFunction) { + if (last instanceof UriResourceFunction) { final UriResourceFunction uriResourceFunction = (UriResourceFunction) context.contextUriInfo .getLastResourcePart(); final EdmReturnType returnType = uriResourceFunction.getFunction().getReturnType(); @@ -1721,12 +1738,12 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { UriParserSemanticException.MessageKeys.KEY_NOT_ALLOWED)); } else { // The functions returns a collection of entities - // Get the EDM Type and determine how many key predicates are needed. + // Get the EDM Type and determine how many key predicates are needed. // In case of functions all key predicates must be provided by the client. final EdmEntityType entityType = (EdmEntityType) uriResourceFunction.getFunction().getReturnType().getType(); final List lastKeyPredicates = entityType.getKeyPredicateNames(); - - if(lastKeyPredicates.size() == list.size()) { + + if (lastKeyPredicates.size() == list.size()) { return list; } else { throw wrap(new UriParserSemanticException("Wrong number of key properties.", @@ -1737,15 +1754,15 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { } else { // Handle entity sets EdmEntityType lastType = (EdmEntityType) ((UriResourcePartTyped) last).getType(); - + // get list of keys for lastType List lastKeyPredicates = lastType.getKeyPredicateNames(); - + // check if all key are filled from the URI if (list.size() == lastKeyPredicates.size()) { return list; } - + // if not, check if the missing key predicates can be satisfied with help of the defined // referential constraints // for using referential constraints the last resource part must be a navigation property @@ -1755,7 +1772,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { Integer.toString(lastKeyPredicates.size()), Integer.toString(list.size()))); } UriResourceNavigationPropertyImpl lastNav = (UriResourceNavigationPropertyImpl) last; - + // get the partner of the navigation property EdmNavigationProperty partner = lastNav.getProperty().getPartner(); if (partner == null) { @@ -1763,7 +1780,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { UriParserSemanticException.MessageKeys.WRONG_NUMBER_OF_KEY_PROPERTIES, Integer.toString(lastKeyPredicates.size()), Integer.toString(list.size()))); } - + // fill missing keys from referential constraints for (String key : lastKeyPredicates) { boolean found = false; @@ -1773,7 +1790,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { break; } } - + if (!found) { String property = partner.getReferencingPropertyName(key); if (property != null) { @@ -1782,7 +1799,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { } } } - + // check again if all key predicates are filled from the URI if (list.size() == lastKeyPredicates.size()) { return list; @@ -1794,7 +1811,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { } } else { // No key predicates are provided by the client - + if (context.contextReadingFunctionParameters) { return Collections.emptyList(); } else { @@ -1825,8 +1842,8 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { @Override public Object visitNaninfinityLiteral(final NaninfinityLiteralContext ctx) { - return new LiteralImpl().setType(EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Decimal)). - setText(ctx.getText()); + return new LiteralImpl().setType(EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Decimal)).setText(ctx + .getText()); } @Override @@ -1863,7 +1880,8 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { @Override public Object visitOrderByEOF(final OrderByEOFContext ctx) { - + context.contextReadingQueryPart = true; + OrderByOptionImpl orderBy = new OrderByOptionImpl(); for (OrderByItemContext item : ((OrderListContext) ctx.getChild(0)).vlOI) { @@ -1871,6 +1889,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { orderBy.addOrder(oItem); } + context.contextReadingFunctionParameters = false; return orderBy; } @@ -1902,11 +1921,11 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { // check for keyPredicates if (pathInfoSegment instanceof UriResourceWithKeysImpl) { if (ctx.vlNVO.size() > 1) { - throw wrap(new UriParserSemanticException("More than one key predicates found", + throw wrap(new UriParserSemanticException("More than one key predicates found", UriParserSemanticException.MessageKeys.WRONG_NUMBER_OF_KEY_PROPERTIES, "1", Integer.toString(ctx.vlNVO.size()))); } - + @SuppressWarnings("unchecked") List list = (List) ctx.vlNVO.get(0).accept(this); ((UriResourceWithKeysImpl) pathInfoSegment) @@ -1937,7 +1956,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { @Override public Object visitPrimitiveLiteral(final PrimitiveLiteralContext ctx) { ParseTree child1 = ctx.children.get(0); - + if (child1 instanceof EnumLiteralContext || child1 instanceof BooleanNonCaseLiteralContext || child1 instanceof NullruleLiteralContext @@ -1954,11 +1973,11 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { || child1 instanceof BinaryLiteralContext) { return child1.accept(this); } - - // TODO Implement geography types and set the proper type + + // TODO Implement geography types and set a proper type return new LiteralImpl().setText(ctx.getText()); } - + @Override public Object visitBinaryLiteral(BinaryLiteralContext ctx) { return new LiteralImpl().setText(ctx.getText()) @@ -1968,14 +1987,14 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { @Override public Object visitStringLiteral(final StringLiteralContext ctx) { return new LiteralImpl().setText(ctx.getText()) - .setType(EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.String)); + .setType(EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.String)); } - + @Override public Object visitDecimalLiteral(final DecimalLiteralContext ctx) { final EdmType type = EdmPrimitiveTypeFactory.getInstance( - ctx.getText().contains("e") || ctx.getText().contains("E") ? - EdmPrimitiveTypeKind.Double : EdmPrimitiveTypeKind.Decimal); + ctx.getText().contains("e") || ctx.getText().contains("E") ? EdmPrimitiveTypeKind.Double + : EdmPrimitiveTypeKind.Decimal); return new LiteralImpl().setText(ctx.getText()).setType(type); } @@ -1999,7 +2018,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { } return new LiteralImpl().setText(ctx.getText()).setType(type); - } catch( NumberFormatException e) { + } catch (NumberFormatException e) { return new LiteralImpl().setText(ctx.getText()) .setType(EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Decimal)); } @@ -2012,7 +2031,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { } @Override - public Object visitDatetimeoffsetLiteral(final DatetimeoffsetLiteralContext ctx) { + public Object visitDatetimeoffsetLiteral(final DatetimeoffsetLiteralContext ctx) { return new LiteralImpl().setText(ctx.getText()) .setType(EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.DateTimeOffset)); } @@ -2124,13 +2143,17 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { @Override public Object visitSelectEOF(final SelectEOFContext ctx) { + context.contextReadingQueryPart = true; List selectItems = new ArrayList(); for (SelectItemContext si : ctx.vlSI) { selectItems.add((SelectItemImpl) si.accept(this)); } - return new SelectOptionImpl().setSelectItems(selectItems).setText(ctx.getText()); + final QueryOptionImpl result = new SelectOptionImpl().setSelectItems(selectItems).setText(ctx.getText()); + context.contextReadingQueryPart = false; + + return result; } @Override @@ -2492,10 +2515,29 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { alias.setParameter("@" + ctx.odataIdentifier().getChild(0).getText()); return alias; } - + @Override public Object visitSearchSpecialToken(final SearchSpecialTokenContext ctx) { - throw wrap(new UriParserSemanticException("System query option '$search' not implemented!", - UriParserSemanticException.MessageKeys.NOT_IMPLEMENTED, "System query option '$search")); + throw wrap(new UriParserSemanticException("System query option '$search' not implemented!", + UriParserSemanticException.MessageKeys.NOT_IMPLEMENTED, "System query option '$search")); + } + + @Override + public Object visitArrayOrObject(final ArrayOrObjectContext ctx) { + if (!context.contextReadingQueryPart) { + throw wrap(new UriParserSemanticException("Complex parameter are not allowed in resource path", + MessageKeys.COMPLEX_PARAMETER_IN_RESOURCE_PATH, ctx.getText())); + } + + return new LiteralImpl().setText(ctx.getText()).setType(null); + } + + @Override + public Object visitExpandItemsEOF(ExpandItemsEOFContext ctx) { + context.contextReadingQueryPart = true; + final Object result = super.visitExpandItemsEOF(ctx); + context.contextReadingQueryPart = false; + + return result; } } diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java index 6803e7a3f..9135dd84d 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java @@ -79,7 +79,11 @@ public class UriParserSemanticException extends UriParserException { /** parameter: not implemented part */ NOT_IMPLEMENTED, /** parameter: namespace **/ - NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT; + NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT, + /** parameter: complex parameter value */ + COMPLEX_PARAMETER_IN_RESOURCE_PATH, + /** parameter: function import name */ + FUNCTION_IMPORT_NOT_ALLOWED; @Override public String getKey() { diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSyntaxException.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSyntaxException.java index 2a18d5a09..31f69d304 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSyntaxException.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSyntaxException.java @@ -35,7 +35,9 @@ public class UriParserSyntaxException extends UriParserException { /** parameter: $format option value */ WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION_FORMAT, SYSTEM_QUERY_OPTION_LEVELS_NOT_ALLOWED_HERE, - SYNTAX; + SYNTAX, + /** parameter: alias name */ + DUPLICATED_ALIAS; @Override public String getKey() { diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/validator/UriValidationException.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/validator/UriValidationException.java index d883b0c52..01af75dc4 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/validator/UriValidationException.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/validator/UriValidationException.java @@ -37,6 +37,8 @@ public class UriValidationException extends ODataLibraryException { UNSUPPORTED_ACTION_RETURN_TYPE, /** parameter: unsupported http method */ UNSUPPORTED_HTTP_METHOD, + /** parameter: unsupported parameter name */ + UNSUPPORTED_PARAMETER, /** parameter: system query option */ SYSTEM_QUERY_OPTION_NOT_ALLOWED, /** parameters: system query option, http method */ @@ -54,8 +56,11 @@ public class UriValidationException extends ODataLibraryException { /** parameter: unallowed kind before $count */ UNALLOWED_KIND_BEFORE_COUNT, /** parameter: unallowed resource path */ - UNALLOWED_RESOURCE_PATH; + UNALLOWED_RESOURCE_PATH, + /** parameter: missing parameter name */ + MISSING_PARAMETER; + @Override public String getKey() { return name(); diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/validator/UriValidator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/validator/UriValidator.java index feb52eca6..39c73da0f 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/validator/UriValidator.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/validator/UriValidator.java @@ -139,6 +139,7 @@ public class UriValidator { validateForHttpMethod(uriInfo, httpMethod); } validateQueryOptions(uriInfo); + validateParameters(uriInfo); validateKeyPredicates(uriInfo); validatePropertyOperations(uriInfo, httpMethod); } @@ -556,6 +557,58 @@ public class UriValidator { return UriResourceKind.action == uriResourceParts.get(uriResourceParts.size() - 1).getKind(); } + private void validateParameters(final UriInfo uriInfo) throws UriValidationException { + for (UriResource pathSegment : uriInfo.getUriResourceParts()) { + final boolean isFunction = pathSegment.getKind() == UriResourceKind.function; + + if(isFunction) { + final UriResourceFunction functionPathSegement = (UriResourceFunction) pathSegment; + final EdmFunction edmFuntion = functionPathSegement.getFunction(); + + final Map parameters = new HashMap(); + for(final UriParameter parameter : functionPathSegement.getParameters()) { + parameters.put(parameter.getName(), parameter); + } + + boolean firstParameter = true; + for(final String parameterName : edmFuntion.getParameterNames()) { + final UriParameter parameter = parameters.get(parameterName); + final boolean isNullable = edmFuntion.getParameter(parameterName).isNullable(); + + if(parameter != null) { + /** No alias, value explicit null */ + if(parameter.getText() == null + && parameter.getAlias() == null && !isNullable) { + throw new UriValidationException("Missing non nullable parameter " + parameterName, + UriValidationException.MessageKeys.MISSING_PARAMETER, parameterName); + } else if(parameter.getText() == null && parameter.getAlias() != null) { + final String valueForAlias = uriInfo.getValueForAlias(parameter.getAlias()); + /** Alias value is missing or explicit null **/ + if(valueForAlias == null && !isNullable) { + throw new UriValidationException("Missing non nullable parameter " + parameterName, + UriValidationException.MessageKeys.MISSING_PARAMETER, parameterName); + } + } + + parameters.remove(parameterName); + } else if(!isNullable && !(firstParameter && edmFuntion.isBound())) { + // The first parameter of bound functions is implicit provided by the preceding path segment + throw new UriValidationException("Missing non nullable parameter " + parameterName, + UriValidationException.MessageKeys.MISSING_PARAMETER, parameterName); + } + + firstParameter = false; + } + + if(!parameters.isEmpty()) { + final String parameterName = parameters.keySet().iterator().next(); + throw new UriValidationException("Unsupported parameter " + parameterName, + UriValidationException.MessageKeys.UNSUPPORTED_PARAMETER, parameterName); + } + } + } + } + private void validateKeyPredicates(final UriInfo uriInfo) throws UriValidationException { for (UriResource pathSegment : uriInfo.getUriResourceParts()) { final boolean isEntitySet = pathSegment.getKind() == UriResourceKind.entitySet; diff --git a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties index 267db110e..08e9eb8e0 100644 --- a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties +++ b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties @@ -35,6 +35,7 @@ UriParserSyntaxException.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION=The system query op UriParserSyntaxException.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION_FORMAT=The system query option '$format' must be either 'json', 'xml', 'atom', or a valid content type; the value '%1$s' is neither. UriParserSyntaxException.SYSTEM_QUERY_OPTION_LEVELS_NOT_ALLOWED_HERE=The system query option '$levels' is not allowed here. UriParserSyntaxException.SYNTAX=The URI is malformed. +UriParserSyntaxException.DUPLICATED_ALIAS=Duplicated alias. An alias '%1$s' was already specified!. UriParserSemanticException.FUNCTION_NOT_FOUND=The function import '%1$s' has no function with parameters '%2$s'. UriParserSemanticException.RESOURCE_PART_ONLY_FOR_TYPED_PARTS='%1$s' is only allowed for typed parts. @@ -68,6 +69,8 @@ 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.FUNCTION_IMPORT_NOT_ALLOWED=Function Imports are not allowed in $filter or $orderby. Found: '%1$s'. UriValidationException.UNSUPPORTED_QUERY_OPTION=The query option '%1$s' is not supported. UriValidationException.UNSUPPORTED_URI_KIND=The URI kind '%1$s' is not supported. @@ -75,6 +78,7 @@ UriValidationException.UNSUPPORTED_URI_RESOURCE_KIND=The URI resource kind '%1$s UriValidationException.UNSUPPORTED_FUNCTION_RETURN_TYPE=The function return type '%1$s' is not supported. UriValidationException.UNSUPPORTED_ACTION_RETURN_TYPE=The action return type '%1$s' is not supported. UriValidationException.UNSUPPORTED_HTTP_METHOD=The HTTP method '%1$s' is not supported. +UriValidationException.UNSUPPORTED_PARAMETER=The parameter '%1$s' is not supported. UriValidationException.SYSTEM_QUERY_OPTION_NOT_ALLOWED=The system query option '%1$s' is not allowed. UriValidationException.SYSTEM_QUERY_OPTION_NOT_ALLOWED_FOR_HTTP_METHOD=The system query option '%1$s' is not allowed for HTTP method '%2$s'. UriValidationException.INVALID_KEY_PROPERTY=The key property '%1$s' is invalid. @@ -84,6 +88,7 @@ UriValidationException.SECOND_LAST_SEGMENT_NOT_TYPED=The second last segment '%1 UriValidationException.UNALLOWED_KIND_BEFORE_VALUE=The kind '%1$s' is not allowed before '$value'. UriValidationException.UNALLOWED_KIND_BEFORE_COUNT=The kind '%1$s' is not allowed before '$count'. UriValidationException.UNALLOWED_RESOURCE_PATH=The resource part '%1$s' is not allowed. +UriValidationException.MISSING_PARAMETER=Missing mandatory parameter '%1$s'. ContentNegotiatorException.UNSUPPORTED_ACCEPT_TYPES=The content-type range '%1$s' is not supported as value of the Accept header. ContentNegotiatorException.UNSUPPORTED_CONTENT_TYPE=The content type '%1$s' is not supported. diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/antlr/TestFullResourcePath.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/antlr/TestFullResourcePath.java index 4d39d9791..90f999582 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/antlr/TestFullResourcePath.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/antlr/TestFullResourcePath.java @@ -2036,7 +2036,8 @@ public class TestFullResourcePath { testUri.run("FICRTCollESTwoKeyNavParam(ParameterInt16=@parameterAlias)", "@parameterAlias=1"); testUri.run("FICRTCollESTwoKeyNavParam(ParameterInt16=@parameterAlias)/$count", "@parameterAlias=1"); - testUri.run("FICRTCollESTwoKeyNavParam(ParameterInt16=@invalidAlias)", "@validAlias=1"); + testUri.runEx("FICRTCollESTwoKeyNavParam(ParameterInt16=@invalidAlias)", "@validAlias=1") + .isExValidation(UriValidationException.MessageKeys.MISSING_PARAMETER); } @Test @@ -5658,7 +5659,94 @@ public class TestFullResourcePath { .root() .right().isLiteral("" + Long.MAX_VALUE).isLiteralType(EdmInt64.getInstance()); } + + @Test + public void parameterAliasLiteralValidation() throws Exception { + testUri.run("ESAllPrim(PropertyInt16=@p1)", "@p1=1"); + testUri.run("ESAllPrim(PropertyInt16=@p1)", "@p1=-2"); + testUri.runEx("ESAllPrim(PropertyInt16=@p1)", "@p1='ewe'") + .isExValidation(UriValidationException.MessageKeys.INVALID_KEY_PROPERTY); + testUri.runEx("ESAllPrim(PropertyInt16=@p1)", "@p1='ewe") + .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); + } + + @Test + public void functionsWithComplexParameters() throws Exception { + testUri.run("ESTwoKeyNav/olingo.odata.test1.BFCESTwoKeyNavRTStringParam" + + "(ParameterComp=@p1)", "@p1={\"PropertyInt16\":1,\"ProperyString\":\"1\"}") + .goPath() + .at(0).isEntitySet("ESTwoKeyNav") + .at(1).isFunction("BFCESTwoKeyNavRTStringParam").isParameterAlias(0, "ParameterComp", "@p1") + .isInAliasToValueMap("@p1", "{\"PropertyInt16\":1,\"ProperyString\":\"1\"}"); + + // Test JSON String lexer rule =\"3,Int16=abc},\\\nabc&test%test\b\f\r\t\u0022} + final String stringValueEncoded = "=\\\"3,Int16=abc},\\\\\\nabc%26test%25test\\b\\f\\r\\t\\u0022}"; + final String stringValueDecoded = "=\\\"3,Int16=abc},\\\\\\nabc&test%test\\b\\f\\r\\t\\u0022}"; + testUri.run("ESTwoKeyNav/olingo.odata.test1.BFCESTwoKeyNavRTStringParam" + + "(ParameterComp=@p1)", "@p1={\"PropertyInt16\":1,\"ProperyString\":\"" + stringValueEncoded + "\"}") + .goPath() + .at(0).isEntitySet("ESTwoKeyNav") + .at(1).isFunction("BFCESTwoKeyNavRTStringParam").isParameterAlias(0, "ParameterComp", "@p1") + .isInAliasToValueMap("@p1", "{\"PropertyInt16\":1,\"ProperyString\":\"" + stringValueDecoded + "\"}"); + + testUri.run("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam" + + "(ParameterComp={\"PropertyString\":\"Test\",\"PropertyInt16\":1}) eq 'Test'") + .goFilter().left().is("< eq <'Test'>>") + .isParameterText(0, "{\"PropertyString\":\"Test\",\"PropertyInt16\":1}"); + + testUri.run("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam" + + "(ParameterComp={\"PropertyString\":\"" + stringValueEncoded + "\",\"PropertyInt16\":1}) eq 'Test'") + .goFilter().left().is("< eq <'Test'>>") + .isParameterText(0, "{\"PropertyString\":\"=\\\"3,Int16=abc},\\\\\\nabc&test%test\\b\\f\\r\\t\\u0022}\"," + + "\"PropertyInt16\":1}"); + + testUri.run("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam" + + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":1,\"ProperyString\":\"1\"}"); + + testUri.runEx("ESTwoKeyNav/olingo.odata.test1.BFCESTwoKeyNavRTStringParam" + + "(ParameterComp=@p1)", "@p1={\"PropertyInt16\":1,\"ProperyString\":'1'}") + .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); + + testUri.runEx("ESTwoKeyNav/olingo.odata.test1.BFCESTwoKeyNavRTStringParam" + + "(ParameterComp={\"PropertyInt16\":1,\"PropertyString\":\"Test\"})") + .isExSemantic(UriParserSemanticException.MessageKeys.COMPLEX_PARAMETER_IN_RESOURCE_PATH); + + testUri.runEx("FICRTCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=null)") + .isExValidation(UriValidationException.MessageKeys.MISSING_PARAMETER); + + testUri.runEx("FICRTCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test)") + .isExValidation(UriValidationException.MessageKeys.MISSING_PARAMETER); + + testUri.runEx("FICRTCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test)", "@test=null") + .isExValidation(UriValidationException.MessageKeys.MISSING_PARAMETER); + + testUri.run("FICRTCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test)", "@test='null'"); + + testUri.runEx("FICRTCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test, UnknownParam=1)", "@test='null'") + .isExSemantic(UriParserSemanticException.MessageKeys.FUNCTION_NOT_FOUND); + + testUri.run("FICRTCollCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test)", "@test='null'"); + testUri.run("FICRTCollCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test)", "@test=null"); + testUri.run("FICRTCollCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test)"); + testUri.run("FICRTCollCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=null)"); + + testUri.runEx("FICRTCollCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test)", "@test=null&@test='1'") + .isExSyntax(UriParserSyntaxException.MessageKeys.DUPLICATED_ALIAS); + + testUri.runEx("ESAllPrim", "$filter=FINRTInt16() eq 0") + .isExSemantic(UriParserSemanticException.MessageKeys.FUNCTION_IMPORT_NOT_ALLOWED); + } + + @Test + @Ignore("Key predicates in filter/orderby expression are not validated currently") + public void testKeyPredicatesInExpressions() throws Exception { + testUri.run("ESTwoKeyNav", "$filter=NavPropertyETTwoKeyNavMany(PropertyString='1',PropertyInt16=1)" + + "/PropertyInt16 eq 1"); + testUri.runEx("ESTwoKeyNav", "$filter=NavPropertyETTwoKeyNavMany(Prop='22',P=2)/PropertyInt16 eq 0") + .isExValidation(UriValidationException.MessageKeys.INVALID_KEY_PROPERTY); + } + public static String encode(final String decoded) throws UnsupportedEncodingException { return Encoder.encode(decoded); } diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/antlr/TestUriParserImpl.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/antlr/TestUriParserImpl.java index b115fb889..b171c0441 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/antlr/TestUriParserImpl.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/antlr/TestUriParserImpl.java @@ -1061,7 +1061,7 @@ public class TestUriParserImpl { @Test public void testAlias() throws Exception { - testUri.run("ESAllPrim", "$filter=PropertyInt16 eq @p1&@p1=1)") + testUri.run("ESAllPrim", "$filter=PropertyInt16 eq @p1&@p1=1") .goFilter().is("< eq <@p1>>"); } diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/EdmTechTestProvider.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/EdmTechTestProvider.java index 08c6c91f3..2ab43e1ea 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/EdmTechTestProvider.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/EdmTechTestProvider.java @@ -27,6 +27,7 @@ import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.edm.provider.CsdlComplexType; import org.apache.olingo.commons.api.edm.provider.CsdlEntitySet; import org.apache.olingo.commons.api.edm.provider.CsdlEntityType; +import org.apache.olingo.commons.api.edm.provider.CsdlFunctionImport; import org.apache.olingo.commons.api.edm.provider.CsdlNavigationProperty; import org.apache.olingo.commons.api.edm.provider.CsdlProperty; import org.apache.olingo.commons.api.edm.provider.CsdlPropertyRef; @@ -68,7 +69,7 @@ public class EdmTechTestProvider extends EdmTechProvider { )); } - + return super.getComplexType(complexTypeName); } @@ -115,5 +116,13 @@ public class EdmTechTestProvider extends EdmTechProvider { return super.getEntityType(entityTypeName); } - + + @Override + public CsdlFunctionImport getFunctionImport(FullQualifiedName entityContainer, String functionImportName) + throws ODataException { + + + return super.getFunctionImport(entityContainer, functionImportName); + } + } diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterValidator.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterValidator.java index 02cf29ec5..fa0e5379e 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterValidator.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/FilterValidator.java @@ -31,12 +31,14 @@ import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriInfoKind; +import org.apache.olingo.server.api.uri.UriResource; import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind; import org.apache.olingo.server.api.uri.queryoption.expression.Expression; import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitException; import org.apache.olingo.server.api.uri.queryoption.expression.Member; import org.apache.olingo.server.api.uri.queryoption.expression.MethodKind; import org.apache.olingo.server.core.uri.UriInfoImpl; +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; @@ -403,16 +405,26 @@ public class FilterValidator implements TestValidator { public FilterValidator isParameterText(final int parameterIndex, final String parameterText) throws ExpressionVisitException, ODataApplicationException { - if (!(curExpression instanceof MethodImpl)) { - fail("Current expression is not a method"); + if (curExpression instanceof MethodImpl) { + MethodImpl methodCall = (MethodImpl) curExpression; + + Expression parameter = methodCall.getParameters().get(parameterIndex); + String actualParameterText = FilterTreeToText.Serialize(parameter); + assertEquals(parameterText, actualParameterText); + } else if (curExpression instanceof MemberImpl) { + final MemberImpl member = (MemberImpl) curExpression; + final List uriResourceParts = member.getResourcePath().getUriResourceParts(); + + if (!uriResourceParts.isEmpty() && uriResourceParts.get(0) instanceof UriResourceFunctionImpl) { + assertEquals(parameterText, ((UriResourceFunctionImpl) uriResourceParts.get(0)).getParameters() + .get(parameterIndex).getText()); + } else { + fail("Current expression is not a method or function"); + } + } else { + fail("Current expression is not a method or function"); } - MethodImpl methodCall = (MethodImpl) curExpression; - - Expression parameter = methodCall.getParameters().get(parameterIndex); - String actualParameterText = FilterTreeToText.Serialize(parameter); - assertEquals(parameterText, actualParameterText); - return this; }