[OLINGO-834] better alias support in URI parser Signed-off-by: Christian Amend <[email protected]>
Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/110c7b0e Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/110c7b0e Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/110c7b0e Branch: refs/heads/master Commit: 110c7b0e5afa1f9f717d95ca48bd5c695821db06 Parents: b317b90 Author: Klaus Straubinger <[email protected]> Authored: Thu Feb 4 12:24:38 2016 +0100 Committer: Christian Amend <[email protected]> Committed: Thu Feb 4 12:38:10 2016 +0100 ---------------------------------------------------------------------- .../uri/queryoption/SystemQueryOptionKind.java | 22 +- .../apache/olingo/server/core/ODataHandler.java | 4 +- .../server/core/ODataHttpHandlerImpl.java | 19 +- .../olingo/server/core/uri/UriInfoImpl.java | 37 +- .../server/core/uri/parser/ExpandParser.java | 30 +- .../core/uri/parser/ExpressionParser.java | 85 ++- .../server/core/uri/parser/FilterParser.java | 6 +- .../server/core/uri/parser/OrderByParser.java | 6 +- .../olingo/server/core/uri/parser/Parser.java | 354 +++++++------ .../server/core/uri/parser/ParserHelper.java | 168 ++++-- .../core/uri/parser/ResourcePathParser.java | 38 +- .../server/core/uri/parser/SelectParser.java | 12 +- .../server/core/uri/parser/UriTokenizer.java | 12 +- .../uri/queryoption/expression/AliasImpl.java | 9 +- .../uri/validator/UriValidationException.java | 4 +- .../server/core/uri/validator/UriValidator.java | 513 +++++-------------- .../server-core-exceptions-i18n.properties | 1 + .../olingo/server/core/uri/UriInfoImplTest.java | 50 +- .../core/uri/parser/ExpressionParserTest.java | 2 +- .../core/uri/parser/UriTokenizerTest.java | 3 + .../olingo/server/tecsvc/data/DataProvider.java | 12 +- .../SystemQueryOptionsRuntimeException.java | 3 +- .../tecsvc/provider/ContainerProvider.java | 6 +- .../core/uri/parser/TestFullResourcePath.java | 112 ++-- .../core/uri/queryoption/QueryOptionTest.java | 4 +- .../queryoption/expression/ExpressionTest.java | 6 +- .../core/uri/testutil/FilterValidator.java | 51 +- .../core/uri/validator/UriValidatorTest.java | 68 +-- 28 files changed, 797 insertions(+), 840 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/110c7b0e/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java ---------------------------------------------------------------------- diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java index f0c907b..d5d60a1 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/SystemQueryOptionKind.java @@ -18,10 +18,10 @@ */ package org.apache.olingo.server.api.uri.queryoption; + /** * Defines the supported system query options. */ - public enum SystemQueryOptionKind { /** @@ -84,12 +84,30 @@ public enum SystemQueryOptionKind { */ LEVELS("$levels"); - private String syntax; + private final String syntax; SystemQueryOptionKind(final String syntax) { this.syntax = syntax; } + /** + * Converts the URI syntax to an enumeration value. + * @param option option in the syntax used in the URI + * @return system query option kind representing the given option + * (or <code>null</code> if the option does not represent a system query option) + */ + public static SystemQueryOptionKind get(final String option) { + for (final SystemQueryOptionKind kind : values()) { + if (kind.syntax.equals(option)) { + return kind; + } + } + return null; + } + + /** + * @return URI syntax for this system query option + */ @Override public String toString() { return syntax; http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/110c7b0e/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java index 96b419c..3baaac7 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java @@ -160,6 +160,7 @@ public class ODataHandler { public void handleException(final ODataRequest request, final ODataResponse response, final ODataServerError serverError, final Exception exception) { + final int measurementHandle = debugger.startRuntimeMeasurement("ODataHandler", "handleException"); lastThrownException = exception; ErrorProcessor exceptionProcessor; try { @@ -176,8 +177,9 @@ public class ODataHandler { } catch (final ContentNegotiatorException e) { requestedContentType = ContentType.JSON; } - final int measurementHandle = debugger.startRuntimeMeasurement("ErrorProcessor", "processError"); + final int measurementError = debugger.startRuntimeMeasurement("ErrorProcessor", "processError"); exceptionProcessor.processError(request, response, serverError, requestedContentType); + debugger.stopRuntimeMeasurement(measurementError); debugger.stopRuntimeMeasurement(measurementHandle); } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/110c7b0e/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHttpHandlerImpl.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHttpHandlerImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHttpHandlerImpl.java index 55c1194..61581c0 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHttpHandlerImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHttpHandlerImpl.java @@ -24,7 +24,7 @@ import java.nio.channels.Channel; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; -import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.List; @@ -191,8 +191,12 @@ public class ODataHttpHandlerImpl implements ODataHttpHandler { odRequest.setBody(httpRequest.getInputStream()); odRequest.setProtocol(httpRequest.getProtocol()); odRequest.setMethod(extractMethod(httpRequest)); + int innerHandle = debugger.startRuntimeMeasurement("ODataHttpHandlerImpl", "copyHeaders"); copyHeaders(odRequest, httpRequest); + debugger.stopRuntimeMeasurement(innerHandle); + innerHandle = debugger.startRuntimeMeasurement("ODataHttpHandlerImpl", "fillUriInformation"); fillUriInformation(odRequest, httpRequest, split); + debugger.stopRuntimeMeasurement(innerHandle); return odRequest; } catch (final IOException e) { @@ -273,14 +277,11 @@ public class ODataHttpHandlerImpl implements ODataHttpHandler { odRequest.setRawServiceResolutionUri(rawServiceResolutionUri); } - static void copyHeaders(final ODataRequest odRequest, final HttpServletRequest req) { - for (Enumeration<?> headerNames = req.getHeaderNames(); headerNames.hasMoreElements();) { - String headerName = (String) headerNames.nextElement(); - List<String> headerValues = new ArrayList<String>(); - for (Enumeration<?> headers = req.getHeaders(headerName); headers.hasMoreElements();) { - String value = (String) headers.nextElement(); - headerValues.add(value); - } + static void copyHeaders(ODataRequest odRequest, final HttpServletRequest req) { + for (final Enumeration<?> headerNames = req.getHeaderNames(); headerNames.hasMoreElements();) { + final String headerName = (String) headerNames.nextElement(); + @SuppressWarnings("unchecked") // getHeaders() says it returns an Enumeration of String. + final List<String> headerValues = Collections.list(req.getHeaders(headerName)); odRequest.addHeader(headerName, headerValues); } } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/110c7b0e/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriInfoImpl.java ---------------------------------------------------------------------- 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 e6fe057..7183bde 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 @@ -20,6 +20,7 @@ package org.apache.olingo.server.core.uri; import java.util.ArrayList; import java.util.Collections; +import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -64,7 +65,7 @@ public class UriInfoImpl implements UriInfo { private List<UriResource> pathParts = new ArrayList<UriResource>(); private Map<SystemQueryOptionKind, SystemQueryOption> systemQueryOptions = - new HashMap<SystemQueryOptionKind, SystemQueryOption>(); + new EnumMap<SystemQueryOptionKind, SystemQueryOption>(SystemQueryOptionKind.class); private Map<String, AliasQueryOption> aliases = new HashMap<String, AliasQueryOption>(); private List<CustomQueryOption> customQueryOptions = new ArrayList<CustomQueryOption>(); @@ -155,15 +156,13 @@ public class UriInfoImpl implements UriInfo { return Collections.unmodifiableList(pathParts); } - public UriInfoImpl setQueryOptions(final List<QueryOption> list) { - for (final QueryOption item : list) { - if (item instanceof SystemQueryOption) { - setSystemQueryOption((SystemQueryOption) item); - } else if (item instanceof AliasQueryOption) { - addAlias((AliasQueryOption) item); - } else if (item instanceof CustomQueryOption) { - addCustomQueryOption((CustomQueryOption) item); - } + public UriInfoImpl setQueryOption(final QueryOption option) { + if (option instanceof SystemQueryOption) { + setSystemQueryOption((SystemQueryOption) option); + } else if (option instanceof AliasQueryOption) { + addAlias((AliasQueryOption) option); + } else if (option instanceof CustomQueryOption) { + addCustomQueryOption((CustomQueryOption) option); } return this; } @@ -263,18 +262,22 @@ public class UriInfoImpl implements UriInfo { } public UriInfoImpl addAlias(final AliasQueryOption alias) { - aliases.put(alias.getName(), alias); + if (aliases.containsKey(alias.getName())) { + throw new ODataRuntimeException("Alias " + alias.getName() + " is already there."); + } else { + aliases.put(alias.getName(), alias); + } return this; } @Override public String getValueForAlias(final String alias) { - final AliasQueryOption aliasQueryOption = getAlias(alias); + final AliasQueryOption aliasQueryOption = aliases.get(alias); return aliasQueryOption == null ? null : aliasQueryOption.getText(); } - public AliasQueryOption getAlias(final String key) { - return aliases.get(key); + public Map<String, AliasQueryOption> getAliasMap() { + return Collections.unmodifiableMap(aliases); } @Override @@ -282,8 +285,10 @@ public class UriInfoImpl implements UriInfo { return Collections.unmodifiableList(new ArrayList<AliasQueryOption>(aliases.values())); } - public UriInfoImpl addCustomQueryOption(final CustomQueryOption item) { - customQueryOptions.add(item); + public UriInfoImpl addCustomQueryOption(final CustomQueryOption option) { + if (option.getName() != null && !option.getName().isEmpty()) { + customQueryOptions.add(option); + } return this; } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/110c7b0e/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpandParser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpandParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpandParser.java index d8209d8..03750a5 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpandParser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpandParser.java @@ -18,6 +18,8 @@ */ package org.apache.olingo.server.core.uri.parser; +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.EdmNavigationProperty; @@ -30,6 +32,7 @@ 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.AliasQueryOption; 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; @@ -53,10 +56,12 @@ public class ExpandParser { private final Edm edm; private final OData odata; + private final Map<String, AliasQueryOption> aliases; - public ExpandParser(final Edm edm, final OData odata) { + public ExpandParser(final Edm edm, final OData odata, final Map<String, AliasQueryOption> aliases) { this.edm = edm; this.odata = odata; + this.aliases = aliases; } public ExpandOption parse(UriTokenizer tokenizer, final EdmStructuredType referencedType) @@ -92,7 +97,7 @@ public class ExpandParser { ParserHelper.requireNext(tokenizer, TokenKind.SLASH); } - UriInfoImpl resource = parseExpandPath(tokenizer, referencedType); + UriInfoImpl resource = parseExpandPath(tokenizer, referencedType, item); UriResourcePartTyped lastPart = (UriResourcePartTyped) resource.getLastResourcePart(); @@ -156,8 +161,8 @@ public class ExpandParser { return null; } - private UriInfoImpl parseExpandPath(UriTokenizer tokenizer, final EdmStructuredType referencedType) - throws UriParserException { + private UriInfoImpl parseExpandPath(UriTokenizer tokenizer, final EdmStructuredType referencedType, + ExpandItemImpl item) throws UriParserException { UriInfoImpl resource = new UriInfoImpl().setKind(UriInfoKind.resource); EdmStructuredType type = referencedType; @@ -181,10 +186,13 @@ public class ExpandParser { 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); + if (tokenizer.next(TokenKind.STAR)) { + item.setIsStar(true); + } else { + 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)); } @@ -209,11 +217,11 @@ public class ExpandParser { } else if (!forRef && !forCount && tokenizer.next(TokenKind.EXPAND)) { ParserHelper.requireNext(tokenizer, TokenKind.EQ); - systemQueryOption = new ExpandParser(edm, odata).parse(tokenizer, referencedType); + systemQueryOption = new ExpandParser(edm, odata, aliases).parse(tokenizer, referencedType); } else if (tokenizer.next(TokenKind.FILTER)) { ParserHelper.requireNext(tokenizer, TokenKind.EQ); - systemQueryOption = new FilterParser(edm, odata).parse(tokenizer, referencedType, null); + systemQueryOption = new FilterParser(edm, odata).parse(tokenizer, referencedType, null, aliases); } else if (!forRef && !forCount && tokenizer.next(TokenKind.LEVELS)) { ParserHelper.requireNext(tokenizer, TokenKind.EQ); @@ -221,7 +229,7 @@ public class ExpandParser { } else if (!forCount && tokenizer.next(TokenKind.ORDERBY)) { ParserHelper.requireNext(tokenizer, TokenKind.EQ); - systemQueryOption = new OrderByParser(edm, odata).parse(tokenizer, referencedType, null); + systemQueryOption = new OrderByParser(edm, odata).parse(tokenizer, referencedType, null, aliases); } else if (tokenizer.next(TokenKind.SEARCH)) { ParserHelper.requireNext(tokenizer, TokenKind.EQ); http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/110c7b0e/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java index 1cf279e..a0ec676 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java @@ -24,7 +24,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Deque; -import java.util.HashMap; +import java.util.EnumMap; import java.util.List; import java.util.Map; @@ -53,6 +53,7 @@ import org.apache.olingo.server.api.uri.UriResourceFunction; import org.apache.olingo.server.api.uri.UriResourceLambdaVariable; import org.apache.olingo.server.api.uri.UriResourceNavigation; import org.apache.olingo.server.api.uri.UriResourcePartTyped; +import org.apache.olingo.server.api.uri.queryoption.AliasQueryOption; import org.apache.olingo.server.api.uri.queryoption.expression.Alias; import org.apache.olingo.server.api.uri.queryoption.expression.Binary; import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind; @@ -96,7 +97,7 @@ import org.apache.olingo.server.core.uri.validator.UriValidationException; public class ExpressionParser { private static final Map<TokenKind, BinaryOperatorKind> tokenToBinaryOperator; static { - Map<TokenKind, BinaryOperatorKind> temp = new HashMap<TokenKind, BinaryOperatorKind>(); + Map<TokenKind, BinaryOperatorKind> temp = new EnumMap<TokenKind, BinaryOperatorKind>(TokenKind.class); temp.put(TokenKind.OrOperator, BinaryOperatorKind.OR); temp.put(TokenKind.AndOperator, BinaryOperatorKind.AND); @@ -121,7 +122,7 @@ public class ExpressionParser { // 'cast' and 'isof' are handled specially. private static final Map<TokenKind, MethodKind> tokenToMethod; static { - Map<TokenKind, MethodKind> temp = new HashMap<TokenKind, MethodKind>(); + Map<TokenKind, MethodKind> temp = new EnumMap<TokenKind, MethodKind>(TokenKind.class); temp.put(TokenKind.CeilingMethod, MethodKind.CEILING); temp.put(TokenKind.ConcatMethod, MethodKind.CONCAT); temp.put(TokenKind.ContainsMethod, MethodKind.CONTAINS); @@ -163,6 +164,7 @@ public class ExpressionParser { private Deque<UriResourceLambdaVariable> lambdaVariables = new ArrayDeque<UriResourceLambdaVariable>(); private EdmType referringType; private Collection<String> crossjoinEntitySetNames; + private Map<String, AliasQueryOption> aliases; public ExpressionParser(final Edm edm, final OData odata) { this.edm = edm; @@ -170,12 +172,13 @@ public class ExpressionParser { } public Expression parse(UriTokenizer tokenizer, final EdmType referringType, - final Collection<String> crossjoinEntitySetNames) + final Collection<String> crossjoinEntitySetNames, final Map<String, AliasQueryOption> aliases) throws UriParserException, UriValidationException { // Initialize tokenizer. this.tokenizer = tokenizer; this.referringType = referringType; this.crossjoinEntitySetNames = crossjoinEntitySetNames; + this.aliases = aliases; return parseExpression(); } @@ -339,7 +342,14 @@ public class ExpressionParser { } if (tokenizer.next(TokenKind.ParameterAliasName)) { - return new AliasImpl(tokenizer.getText()); + final String name = tokenizer.getText(); + if (aliases.containsKey(name)) { + return new AliasImpl(name, + ParserHelper.parseAliasValue(name, null, true, true, edm, referringType, aliases)); + } else { + throw new UriValidationException("Alias '" + name + "' not found.", + UriValidationException.MessageKeys.MISSING_ALIAS, name); + } } if (tokenizer.next(TokenKind.jsonArrayOrObject)) { @@ -360,7 +370,8 @@ public class ExpressionParser { return parsePrimitive(nextPrimitive); } - final TokenKind nextMethod = nextMethod(); + final TokenKind nextMethod = + ParserHelper.next(tokenizer, tokenToMethod.keySet().toArray(new TokenKind[tokenToMethod.size()])); if (nextMethod != null) { return parseMethod(nextMethod); } @@ -630,7 +641,7 @@ public class ExpressionParser { } else { ParserHelper.requireNext(tokenizer, TokenKind.OPEN); final List<UriParameter> keyPredicates = - ParserHelper.parseKeyPredicate(tokenizer, entitySet.getEntityType(), null); + ParserHelper.parseKeyPredicate(tokenizer, entitySet.getEntityType(), null, edm, referringType, aliases); resource = new UriResourceEntitySetImpl(entitySet).setKeyPredicates(keyPredicates); } uriInfo.addResourcePart(resource); @@ -777,7 +788,8 @@ public class ExpressionParser { final UriResourceNavigationPropertyImpl navigationResource = new UriResourceNavigationPropertyImpl((EdmNavigationProperty) property); navigationResource.setKeyPredicates( - ParserHelper.parseNavigationKeyPredicate(tokenizer, (EdmNavigationProperty) property)); + ParserHelper.parseNavigationKeyPredicate(tokenizer, (EdmNavigationProperty) property, + edm, referringType, aliases)); uriInfo.addResourcePart(navigationResource); if (navigationResource.isCollection()) { @@ -830,13 +842,16 @@ public class ExpressionParser { if (lastResource instanceof UriResourceNavigation) { ((UriResourceNavigationPropertyImpl) lastResource).setKeyPredicates( ParserHelper.parseNavigationKeyPredicate(tokenizer, - ((UriResourceNavigationPropertyImpl) lastResource).getProperty())); + ((UriResourceNavigationPropertyImpl) lastResource).getProperty(), edm, referringType, aliases)); } else if (lastResource instanceof UriResourceFunction && ((UriResourceFunction) lastResource).getType() instanceof EdmEntityType) { ((UriResourceFunctionImpl) lastResource).setKeyPredicates( ParserHelper.parseKeyPredicate(tokenizer, (EdmEntityType) ((UriResourceFunction) lastResource).getType(), - null)); + null, + edm, + referringType, + aliases)); } else { throw new UriParserSemanticException("Unknown or wrong resource type.", UriParserSemanticException.MessageKeys.NOT_IMPLEMENTED, lastResource.toString()); @@ -911,18 +926,21 @@ public class ExpressionParser { private void parseFunction(final FullQualifiedName fullQualifiedName, UriInfoImpl uriInfo, final EdmType lastType, final boolean lastIsCollection) throws UriParserException, UriValidationException { - final List<UriParameter> parameters = ParserHelper.parseFunctionParameters(tokenizer, edm, referringType, true); + final List<UriParameter> parameters = + ParserHelper.parseFunctionParameters(tokenizer, edm, referringType, true, aliases); final List<String> parameterNames = ParserHelper.getParameterNames(parameters); final EdmFunction boundFunction = edm.getBoundFunction(fullQualifiedName, lastType.getFullQualifiedName(), lastIsCollection, parameterNames); if (boundFunction != null) { + ParserHelper.validateFunctionParameters(boundFunction, parameters, edm, referringType, aliases); parseFunctionRest(uriInfo, boundFunction, parameters); return; } final EdmFunction unboundFunction = edm.getUnboundFunction(fullQualifiedName, parameterNames); if (unboundFunction != null) { + ParserHelper.validateFunctionParameters(unboundFunction, parameters, edm, referringType, aliases); parseFunctionRest(uriInfo, unboundFunction, parameters); return; } @@ -934,7 +952,8 @@ public class ExpressionParser { private void parseBoundFunction(final FullQualifiedName fullQualifiedName, UriInfoImpl uriInfo, final UriResourcePartTyped lastResource) throws UriParserException, UriValidationException { final EdmType type = lastResource.getType(); - final List<UriParameter> parameters = ParserHelper.parseFunctionParameters(tokenizer, edm, referringType, true); + final List<UriParameter> parameters = + ParserHelper.parseFunctionParameters(tokenizer, edm, referringType, true, aliases); final List<String> parameterNames = ParserHelper.getParameterNames(parameters); final EdmFunction boundFunction = edm.getBoundFunction(fullQualifiedName, type.getFullQualifiedName(), lastResource.isCollection(), parameterNames); @@ -942,6 +961,7 @@ public class ExpressionParser { throw new UriParserSemanticException("Bound function '" + fullQualifiedName + "' not found.", UriParserSemanticException.MessageKeys.FUNCTION_NOT_FOUND, fullQualifiedName.getFullQualifiedNameAsString()); } + ParserHelper.validateFunctionParameters(boundFunction, parameters, edm, referringType, aliases); parseFunctionRest(uriInfo, boundFunction, parameters); } @@ -1010,40 +1030,6 @@ public class ExpressionParser { } } - private TokenKind nextMethod() { - return ParserHelper.next(tokenizer, - TokenKind.CeilingMethod, - TokenKind.ConcatMethod, - TokenKind.ContainsMethod, - TokenKind.DateMethod, - TokenKind.DayMethod, - TokenKind.EndswithMethod, - TokenKind.FloorMethod, - TokenKind.FractionalsecondsMethod, - TokenKind.GeoDistanceMethod, - TokenKind.GeoIntersectsMethod, - TokenKind.GeoLengthMethod, - TokenKind.HourMethod, - TokenKind.IndexofMethod, - TokenKind.LengthMethod, - TokenKind.MaxdatetimeMethod, - TokenKind.MindatetimeMethod, - TokenKind.MinuteMethod, - TokenKind.MonthMethod, - TokenKind.NowMethod, - TokenKind.RoundMethod, - TokenKind.SecondMethod, - TokenKind.StartswithMethod, - TokenKind.SubstringMethod, - TokenKind.TimeMethod, - TokenKind.TolowerMethod, - TokenKind.TotaloffsetminutesMethod, - TokenKind.TotalsecondsMethod, - TokenKind.ToupperMethod, - TokenKind.TrimMethod, - TokenKind.YearMethod); - } - protected static EdmType getType(final Expression expression) throws UriParserException { EdmType type; if (expression instanceof Literal) { @@ -1060,11 +1046,12 @@ public class ExpressionParser { type = ((BinaryImpl) expression).getType(); } else if (expression instanceof Method) { type = ((MethodImpl) expression).getType(); + } else if (expression instanceof Alias) { + final AliasQueryOption alias = ((AliasImpl) expression).getAlias(); + type = alias == null || alias.getValue() == null ? null : getType(alias.getValue()); } else if (expression instanceof LambdaRef) { throw new UriParserSemanticException("Type determination not implemented.", UriParserSemanticException.MessageKeys.NOT_IMPLEMENTED, expression.toString()); - } else if (expression instanceof Alias) { - type = null; // The alias would have to be available already parsed. } else { throw new UriParserSemanticException("Unknown expression type.", UriParserSemanticException.MessageKeys.NOT_IMPLEMENTED, expression.toString()); @@ -1142,7 +1129,7 @@ public class ExpressionParser { private Enumeration createEnumExpression(final String primitiveValueLiteral) throws UriParserException { final EdmEnumType enumType = getEnumType(primitiveValueLiteral); - // TODO: Can the Enumeration interface be changed to handle the value as a whole? + // The Enumeration interface could be extended to handle the value as a whole, in line with the primitive type. try { return new EnumerationImpl(enumType, Arrays.asList(enumType.fromUriLiteral(primitiveValueLiteral).split(","))); http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/110c7b0e/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/FilterParser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/FilterParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/FilterParser.java index dd73009..f20b029 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/FilterParser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/FilterParser.java @@ -19,11 +19,13 @@ package org.apache.olingo.server.core.uri.parser; import java.util.Collection; +import java.util.Map; 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.AliasQueryOption; import org.apache.olingo.server.api.uri.queryoption.FilterOption; import org.apache.olingo.server.api.uri.queryoption.expression.Expression; import org.apache.olingo.server.core.uri.queryoption.FilterOptionImpl; @@ -40,10 +42,10 @@ public class FilterParser { } public FilterOption parse(UriTokenizer tokenizer, final EdmType referencedType, - final Collection<String> crossjoinEntitySetNames) + final Collection<String> crossjoinEntitySetNames, final Map<String, AliasQueryOption> aliases) throws UriParserException, UriValidationException { final Expression filterExpression = new ExpressionParser(edm, odata) - .parse(tokenizer, referencedType, crossjoinEntitySetNames); + .parse(tokenizer, referencedType, crossjoinEntitySetNames, aliases); final EdmType type = ExpressionParser.getType(filterExpression); if (type == null || type.equals(odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean))) { return new FilterOptionImpl().setExpression(filterExpression); http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/110c7b0e/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/OrderByParser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/OrderByParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/OrderByParser.java index 5ea8cb7..b008ed6 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/OrderByParser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/OrderByParser.java @@ -19,10 +19,12 @@ package org.apache.olingo.server.core.uri.parser; import java.util.Collection; +import java.util.Map; import org.apache.olingo.commons.api.edm.Edm; import org.apache.olingo.commons.api.edm.EdmStructuredType; import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.uri.queryoption.AliasQueryOption; import org.apache.olingo.server.api.uri.queryoption.OrderByOption; import org.apache.olingo.server.api.uri.queryoption.expression.Expression; import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind; @@ -41,12 +43,12 @@ public class OrderByParser { } public OrderByOption parse(UriTokenizer tokenizer, final EdmStructuredType referencedType, - final Collection<String> crossjoinEntitySetNames) + final Collection<String> crossjoinEntitySetNames, final Map<String, AliasQueryOption> aliases) throws UriParserException, UriValidationException { OrderByOptionImpl orderByOption = new OrderByOptionImpl(); do { final Expression orderByExpression = new ExpressionParser(edm, odata) - .parse(tokenizer, referencedType, crossjoinEntitySetNames); + .parse(tokenizer, referencedType, crossjoinEntitySetNames, aliases); OrderByItemImpl item = new OrderByItemImpl(); item.setExpression(orderByExpression); if (tokenizer.next(TokenKind.AscSuffix)) { http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/110c7b0e/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java index 586cb10..e1313c1 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 @@ -20,8 +20,10 @@ package org.apache.olingo.server.core.uri.parser; import java.util.Collections; 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.EdmStructuredType; import org.apache.olingo.commons.api.edm.EdmType; import org.apache.olingo.commons.api.ex.ODataRuntimeException; @@ -36,21 +38,30 @@ import org.apache.olingo.server.api.uri.UriResourcePartTyped; import org.apache.olingo.server.api.uri.UriResourceRef; import org.apache.olingo.server.api.uri.UriResourceValue; import org.apache.olingo.server.api.uri.queryoption.AliasQueryOption; -import org.apache.olingo.server.api.uri.queryoption.CustomQueryOption; +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.FilterOption; +import org.apache.olingo.server.api.uri.queryoption.OrderByItem; +import org.apache.olingo.server.api.uri.queryoption.OrderByOption; import org.apache.olingo.server.api.uri.queryoption.QueryOption; +import org.apache.olingo.server.api.uri.queryoption.SelectOption; import org.apache.olingo.server.api.uri.queryoption.SystemQueryOption; 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.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.FilterOptionImpl; 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.OrderByOptionImpl; +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.SystemQueryOptionImpl; import org.apache.olingo.server.core.uri.queryoption.TopOptionImpl; import org.apache.olingo.server.core.uri.validator.UriValidationException; @@ -74,6 +85,32 @@ public class Parser { throws UriParserException, UriValidationException { UriInfoImpl contextUriInfo = new UriInfoImpl(); + + // Read the query options (system and custom options). + // This is done before parsing the resource path because the aliases have to be available there. + // System query options that can only be parsed with context from the resource path will be post-processed later. + final List<QueryOption> options = + query == null ? Collections.<QueryOption> emptyList() : UriDecoder.splitAndDecodeOptions(query); + for (final QueryOption option : options) { + final String optionName = option.getName(); + // Parse the untyped option and retrieve a system-option or alias-option instance (or null for a custom option). + final QueryOption parsedOption = parseOption(optionName, option.getText()); + try { + contextUriInfo.setQueryOption(parsedOption == null ? option : parsedOption); + } catch (final ODataRuntimeException e) { + throw new UriParserSyntaxException( + parsedOption instanceof SystemQueryOption ? + "Double system query option!" : + "Alias already specified! Name: " + optionName, + e, + parsedOption instanceof SystemQueryOption ? + UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION : + UriParserSyntaxException.MessageKeys.DUPLICATED_ALIAS, + optionName); + } + } + + // Read the decoded path segments. EdmType contextType = null; boolean contextIsCollection = false; @@ -85,7 +122,6 @@ public class Parser { numberOfSegments--; } - // first, read the decoded path segments final String firstSegment = pathSegmentsDecoded.get(0); if (firstSegment.isEmpty()) { @@ -107,24 +143,29 @@ public class Parser { contextIsCollection = true; } else if (firstSegment.equals("$entity")) { + contextUriInfo.setKind(UriInfoKind.entityId); if (numberOfSegments > 1) { final String typeCastSegment = pathSegmentsDecoded.get(1); ensureLastSegment(typeCastSegment, 2, numberOfSegments); - contextUriInfo = new ResourcePathParser(edm).parseDollarEntityTypeCast(typeCastSegment); - contextType = contextUriInfo.getEntityTypeCast(); - } else { - contextUriInfo.setKind(UriInfoKind.entityId); + contextType = new ResourcePathParser(edm, contextUriInfo.getAliasMap()) + .parseDollarEntityTypeCast(typeCastSegment); + contextUriInfo.setEntityTypeCast((EdmEntityType) contextType); } contextIsCollection = false; } else if (firstSegment.startsWith("$crossjoin")) { ensureLastSegment(firstSegment, 1, numberOfSegments); - contextUriInfo = new ResourcePathParser(edm).parseCrossjoinSegment(firstSegment); + contextUriInfo.setKind(UriInfoKind.crossjoin); + final List<String> entitySetNames = new ResourcePathParser(edm, contextUriInfo.getAliasMap()) + .parseCrossjoinSegment(firstSegment); + for (final String name : entitySetNames) { + contextUriInfo.addEntitySetName(name); + } contextIsCollection = true; } else { contextUriInfo.setKind(UriInfoKind.resource); - final ResourcePathParser resourcePathParser = new ResourcePathParser(edm); + final ResourcePathParser resourcePathParser = new ResourcePathParser(edm, contextUriInfo.getAliasMap()); int count = 0; UriResource lastSegment = null; for (final String pathSegment : pathSegmentsDecoded) { @@ -162,161 +203,168 @@ public class Parser { } } - // second, read the system query options and the custom query options - final List<QueryOption> options = - query == null ? Collections.<QueryOption> emptyList() : UriDecoder.splitAndDecodeOptions(query); - for (final QueryOption option : options) { - final String optionName = option.getName(); - final String optionValue = option.getText(); - if (optionName.startsWith(DOLLAR)) { - SystemQueryOption systemOption = null; - if (optionName.equals(SystemQueryOptionKind.FILTER.toString())) { - UriTokenizer filterTokenizer = new UriTokenizer(optionValue); - // The referring type could be a primitive type or a structured type. - systemOption = new FilterParser(edm, odata).parse(filterTokenizer, - contextType, - contextUriInfo.getEntitySetNames()); - checkOptionEOF(filterTokenizer, optionName, optionValue); - - } else if (optionName.equals(SystemQueryOptionKind.FORMAT.toString())) { - FormatOptionImpl formatOption = new FormatOptionImpl(); - formatOption.setText(optionValue); - if (optionValue.equalsIgnoreCase(JSON) - || optionValue.equalsIgnoreCase(XML) - || optionValue.equalsIgnoreCase(ATOM) - || isFormatSyntaxValid(optionValue)) { - formatOption.setFormat(optionValue); - } else { - throw new UriParserSyntaxException("Illegal value of $format option!", - UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION_FORMAT, optionValue); - } - systemOption = formatOption; - - } else if (optionName.equals(SystemQueryOptionKind.EXPAND.toString())) { - if (contextType instanceof EdmStructuredType - || !contextUriInfo.getEntitySetNames().isEmpty() - || contextUriInfo.getKind() == UriInfoKind.all) { - UriTokenizer expandTokenizer = new UriTokenizer(optionValue); - systemOption = new ExpandParser(edm, odata).parse(expandTokenizer, - contextType instanceof EdmStructuredType ? (EdmStructuredType) contextType : 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; - - } else if (optionName.equals(SystemQueryOptionKind.LEVELS.toString())) { - throw new UriParserSyntaxException("System query option '$levels' is allowed only inside '$expand'!", - UriParserSyntaxException.MessageKeys.SYSTEM_QUERY_OPTION_LEVELS_NOT_ALLOWED_HERE); - - } else if (optionName.equals(SystemQueryOptionKind.ORDERBY.toString())) { - UriTokenizer orderByTokenizer = new UriTokenizer(optionValue); - systemOption = new OrderByParser(edm, odata).parse(orderByTokenizer, - contextType instanceof EdmStructuredType ? (EdmStructuredType) contextType : null, - contextUriInfo.getEntitySetNames()); - checkOptionEOF(orderByTokenizer, optionName, optionValue); - - } else if (optionName.equals(SystemQueryOptionKind.SEARCH.toString())) { - systemOption = new SearchParser().parse(optionValue); - - } else if (optionName.equals(SystemQueryOptionKind.SELECT.toString())) { - UriTokenizer selectTokenizer = new UriTokenizer(optionValue); - systemOption = new SelectParser(edm).parse(selectTokenizer, - contextType instanceof EdmStructuredType ? (EdmStructuredType) contextType : null, - contextIsCollection); - checkOptionEOF(selectTokenizer, optionName, optionValue); - - } else if (optionName.equals(SystemQueryOptionKind.SKIP.toString())) { - SkipOptionImpl skipOption = new SkipOptionImpl(); - skipOption.setText(optionValue); - skipOption.setValue(ParserHelper.parseNonNegativeInteger(optionName, optionValue, true)); - systemOption = skipOption; + // Post-process system query options that need context information from the resource path. + parseFilterOption(contextUriInfo.getFilterOption(), contextType, + contextUriInfo.getEntitySetNames(), contextUriInfo.getAliasMap()); + parseOrderByOption(contextUriInfo.getOrderByOption(), contextType, + contextUriInfo.getEntitySetNames(), contextUriInfo.getAliasMap()); + parseExpandOption(contextUriInfo.getExpandOption(), contextType, + !contextUriInfo.getEntitySetNames().isEmpty() || contextUriInfo.getKind() == UriInfoKind.all, + contextUriInfo.getAliasMap()); + parseSelectOption(contextUriInfo.getSelectOption(), contextType, contextIsCollection); - } 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); - topOption.setValue(ParserHelper.parseNonNegativeInteger(optionName, optionValue, true)); - systemOption = topOption; - - } else if (optionName.equals(SystemQueryOptionKind.COUNT.toString())) { - CountOptionImpl inlineCountOption = new CountOptionImpl(); - inlineCountOption.setText(optionValue); - if (optionValue.equals("true") || optionValue.equals("false")) { - inlineCountOption.setValue(Boolean.parseBoolean(optionValue)); - } else { - throw new UriParserSyntaxException("Illegal value of $count option!", - UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION, - optionName, optionValue); - } - systemOption = inlineCountOption; + return contextUriInfo; + } + private QueryOption parseOption(final String optionName, final String optionValue) + throws UriParserException, UriValidationException { + if (optionName.startsWith(DOLLAR)) { + final SystemQueryOptionKind kind = SystemQueryOptionKind.get(optionName); + if (kind == null) { + throw new UriParserSyntaxException("Unknown system query option!", + UriParserSyntaxException.MessageKeys.UNKNOWN_SYSTEM_QUERY_OPTION, optionName); + } + SystemQueryOption systemOption = null; + switch (kind) { + case SEARCH: + systemOption = new SearchParser().parse(optionValue); + break; + case FILTER: + systemOption = new FilterOptionImpl(); + break; + case COUNT: + if (optionValue.equals("true") || optionValue.equals("false")) { + systemOption = new CountOptionImpl().setValue(Boolean.parseBoolean(optionValue)); } else { - throw new UriParserSyntaxException("Unknown system query option!", - UriParserSyntaxException.MessageKeys.UNKNOWN_SYSTEM_QUERY_OPTION, optionName); + throw new UriParserSyntaxException("Illegal value of $count option!", + UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION, + optionName, optionValue); } - try { - contextUriInfo.setSystemQueryOption(systemOption); - } catch (final ODataRuntimeException e) { - throw new UriParserSyntaxException("Double system query option!", e, - UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, optionName); + break; + case ORDERBY: + systemOption = new OrderByOptionImpl(); + break; + case SKIP: + systemOption = new SkipOptionImpl() + .setValue(ParserHelper.parseNonNegativeInteger(optionName, optionValue, true)); + break; + case SKIPTOKEN: + if (optionValue.isEmpty()) { + throw new UriParserSyntaxException("Illegal value of $skiptoken option!", + UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION, + optionName, optionValue); } - - } else if (optionName.startsWith(AT)) { - if (contextUriInfo.getAlias(optionName) == null) { - // TODO: Aliases can only be parsed in the context of their usage. - Expression expression = null; - UriTokenizer aliasTokenizer = new UriTokenizer(optionValue); - if (aliasTokenizer.next(TokenKind.jsonArrayOrObject)) { - if (!aliasTokenizer.next(TokenKind.EOF)) { - throw new UriParserSyntaxException("Illegal value for alias '" + optionName + "'.", - UriParserSyntaxException.MessageKeys.SYNTAX); - } - } else { - UriTokenizer aliasValueTokenizer = new UriTokenizer(optionValue); - expression = new ExpressionParser(edm, odata).parse(aliasValueTokenizer, null, - contextUriInfo.getEntitySetNames()); - if (!aliasValueTokenizer.next(TokenKind.EOF)) { - throw new UriParserSyntaxException("Illegal value for alias '" + optionName + "'.", - UriParserSyntaxException.MessageKeys.SYNTAX); - } - } - contextUriInfo.addAlias((AliasQueryOption) new AliasQueryOptionImpl() - .setAliasValue(expression) - .setName(optionName) - .setText(NULL.equals(optionValue) ? null : optionValue)); + systemOption = new SkipTokenOptionImpl().setValue(optionValue); + break; + case TOP: + systemOption = new TopOptionImpl() + .setValue(ParserHelper.parseNonNegativeInteger(optionName, optionValue, true)); + break; + case EXPAND: + systemOption = new ExpandOptionImpl(); + break; + case SELECT: + systemOption = new SelectOptionImpl(); + break; + case FORMAT: + if (optionValue.equalsIgnoreCase(JSON) + || optionValue.equalsIgnoreCase(XML) + || optionValue.equalsIgnoreCase(ATOM) + || isFormatSyntaxValid(optionValue)) { + systemOption = new FormatOptionImpl().setFormat(optionValue); } else { - throw new UriParserSyntaxException("Alias already specified! Name: " + optionName, - UriParserSyntaxException.MessageKeys.DUPLICATED_ALIAS, optionName); + throw new UriParserSyntaxException("Illegal value of $format option!", + UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION_FORMAT, optionValue); } + break; + case ID: + if (optionValue.isEmpty()) { + throw new UriParserSyntaxException("Illegal value of $id option!", + UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION, + optionName, optionValue); + } + systemOption = new IdOptionImpl().setValue(optionValue); + break; + case LEVELS: + throw new UriParserSyntaxException("System query option '$levels' is allowed only inside '$expand'!", + UriParserSyntaxException.MessageKeys.SYSTEM_QUERY_OPTION_LEVELS_NOT_ALLOWED_HERE); + } + ((SystemQueryOptionImpl) systemOption).setText(optionValue); + return systemOption; - } else if (!optionName.isEmpty()) { - contextUriInfo.addCustomQueryOption((CustomQueryOption) option); + } else if (optionName.startsWith(AT)) { + // Aliases can only be parsed in the context of their usage, so the value is not checked here. + return new AliasQueryOptionImpl() + .setName(optionName) + .setText(NULL.equals(optionValue) ? null : optionValue); + + } else { + // The option is a custom query option; the caller can re-use its query option. + return null; + } + } + + private void parseFilterOption(FilterOption filterOption, final EdmType contextType, + final List<String> entitySetNames, final Map<String, AliasQueryOption> aliases) + throws UriParserException, UriValidationException { + if (filterOption != null) { + final String optionValue = filterOption.getText(); + UriTokenizer filterTokenizer = new UriTokenizer(optionValue); + // The referring type could be a primitive type or a structured type. + ((FilterOptionImpl) filterOption).setExpression( + new FilterParser(edm, odata).parse(filterTokenizer, contextType, entitySetNames, aliases) + .getExpression()); + checkOptionEOF(filterTokenizer, filterOption.getName(), optionValue); + } + } + + private void parseOrderByOption(OrderByOption orderByOption, final EdmType contextType, + final List<String> entitySetNames, final Map<String, AliasQueryOption> aliases) + throws UriParserException, UriValidationException { + if (orderByOption != null) { + final String optionValue = orderByOption.getText(); + UriTokenizer orderByTokenizer = new UriTokenizer(optionValue); + final OrderByOption option = new OrderByParser(edm, odata).parse(orderByTokenizer, + contextType instanceof EdmStructuredType ? (EdmStructuredType) contextType : null, + entitySetNames, + aliases); + checkOptionEOF(orderByTokenizer, orderByOption.getName(), optionValue); + for (final OrderByItem item : option.getOrders()) { + ((OrderByOptionImpl) orderByOption).addOrder(item); } } + } - return contextUriInfo; + private void parseExpandOption(ExpandOption expandOption, final EdmType contextType, final boolean isCrossjoinOrAll, + final Map<String, AliasQueryOption> aliases) throws UriParserException, UriValidationException { + if (expandOption != null) { + if (!(contextType instanceof EdmStructuredType || isCrossjoinOrAll)) { + throw new UriValidationException("Expand is only allowed on structured types!", + UriValidationException.MessageKeys.SYSTEM_QUERY_OPTION_NOT_ALLOWED, expandOption.getName()); + } + final String optionValue = expandOption.getText(); + UriTokenizer expandTokenizer = new UriTokenizer(optionValue); + final ExpandOption option = new ExpandParser(edm, odata, aliases).parse(expandTokenizer, + contextType instanceof EdmStructuredType ? (EdmStructuredType) contextType : null); + checkOptionEOF(expandTokenizer, expandOption.getName(), optionValue); + for (final ExpandItem item : option.getExpandItems()) { + ((ExpandOptionImpl) expandOption).addExpandItem(item); + } + } + } + + private void parseSelectOption(SelectOption selectOption, final EdmType contextType, + final boolean contextIsCollection) throws UriParserException, UriValidationException { + if (selectOption != null) { + final String optionValue = selectOption.getText(); + UriTokenizer selectTokenizer = new UriTokenizer(optionValue); + ((SelectOptionImpl) selectOption).setSelectItems( + new SelectParser(edm).parse(selectTokenizer, + contextType instanceof EdmStructuredType ? (EdmStructuredType) contextType : null, + contextIsCollection) + .getSelectItems()); + checkOptionEOF(selectTokenizer, selectOption.getName(), optionValue); + } } private void ensureLastSegment(final String segment, final int pos, final int size) http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/110c7b0e/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ParserHelper.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ParserHelper.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ParserHelper.java index b0c2972..9986542 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ParserHelper.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ParserHelper.java @@ -20,6 +20,7 @@ package org.apache.olingo.server.core.uri.parser; import java.util.ArrayList; import java.util.Collections; +import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -27,8 +28,10 @@ import java.util.Map.Entry; import org.apache.olingo.commons.api.edm.Edm; import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.edm.EdmFunction; import org.apache.olingo.commons.api.edm.EdmKeyPropertyRef; import org.apache.olingo.commons.api.edm.EdmNavigationProperty; +import org.apache.olingo.commons.api.edm.EdmParameter; import org.apache.olingo.commons.api.edm.EdmPrimitiveType; import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; @@ -39,6 +42,7 @@ 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.AliasQueryOption; 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; @@ -46,6 +50,8 @@ import org.apache.olingo.server.core.uri.UriParameterImpl; import org.apache.olingo.server.core.uri.UriResourceTypedImpl; import org.apache.olingo.server.core.uri.UriResourceWithKeysImpl; import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind; +import org.apache.olingo.server.core.uri.queryoption.AliasQueryOptionImpl; +import org.apache.olingo.server.core.uri.queryoption.expression.LiteralImpl; import org.apache.olingo.server.core.uri.validator.UriValidationException; public class ParserHelper { @@ -55,7 +61,7 @@ public class ParserHelper { protected static final Map<TokenKind, EdmPrimitiveTypeKind> tokenToPrimitiveType; static { /* Enum and null are not present in the map. These have to be handled differently. */ - Map<TokenKind, EdmPrimitiveTypeKind> temp = new HashMap<TokenKind, EdmPrimitiveTypeKind>(); + Map<TokenKind, EdmPrimitiveTypeKind> temp = new EnumMap<TokenKind, EdmPrimitiveTypeKind>(TokenKind.class); temp.put(TokenKind.BooleanValue, EdmPrimitiveTypeKind.Boolean); temp.put(TokenKind.StringValue, EdmPrimitiveTypeKind.String); // Very large integer values are of type Edm.Decimal but this is handled elsewhere. @@ -87,18 +93,18 @@ public class ParserHelper { tokenToPrimitiveType = Collections.unmodifiableMap(temp); } - public static void requireNext(UriTokenizer tokenizer, final TokenKind required) throws UriParserException { + protected static void requireNext(UriTokenizer tokenizer, final TokenKind required) throws UriParserException { if (!tokenizer.next(required)) { throw new UriParserSyntaxException("Expected token '" + required.toString() + "' not found.", UriParserSyntaxException.MessageKeys.SYNTAX); } } - public static void requireTokenEnd(UriTokenizer tokenizer) throws UriParserException { + protected static void requireTokenEnd(UriTokenizer tokenizer) throws UriParserException { requireNext(tokenizer, TokenKind.EOF); } - public static TokenKind next(UriTokenizer tokenizer, final TokenKind... kinds) { + protected static TokenKind next(UriTokenizer tokenizer, final TokenKind... kinds) { for (final TokenKind kind : kinds) { if (tokenizer.next(kind)) { return kind; @@ -107,7 +113,7 @@ public class ParserHelper { return null; } - public static TokenKind nextPrimitiveValue(UriTokenizer tokenizer) { + protected static TokenKind nextPrimitiveValue(UriTokenizer tokenizer) { return next(tokenizer, TokenKind.NULL, TokenKind.BooleanValue, @@ -146,7 +152,8 @@ public class ParserHelper { } protected static List<UriParameter> parseFunctionParameters(UriTokenizer tokenizer, - final Edm edm, final EdmType referringType, final boolean withComplex) + final Edm edm, final EdmType referringType, final boolean withComplex, + final Map<String, AliasQueryOption> aliases) throws UriParserException, UriValidationException { List<UriParameter> parameters = new ArrayList<UriParameter>(); ParserHelper.requireNext(tokenizer, TokenKind.OPEN); @@ -164,40 +171,105 @@ public class ParserHelper { if (tokenizer.next(TokenKind.COMMA) || tokenizer.next(TokenKind.CLOSE) || tokenizer.next(TokenKind.EOF)) { throw new UriParserSyntaxException("Parameter value expected.", UriParserSyntaxException.MessageKeys.SYNTAX); } + UriParameterImpl parameter = new UriParameterImpl().setName(name); if (tokenizer.next(TokenKind.ParameterAliasName)) { - parameters.add(new UriParameterImpl().setName(name).setAlias(tokenizer.getText())); + final String aliasName = tokenizer.getText(); + parameter.setAlias(aliasName) + .setExpression(aliases.containsKey(aliasName) ? aliases.get(aliasName).getValue() : null); } else if (tokenizer.next(TokenKind.jsonArrayOrObject)) { if (withComplex) { - parameters.add(new UriParameterImpl().setName(name).setText(tokenizer.getText())); + parameter.setText(tokenizer.getText()); } else { 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)); + final Expression expression = new ExpressionParser(edm, odata).parse(tokenizer, referringType, null, aliases); + parameter.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, ""); } else { final String literalValue = tokenizer.getText(); - parameters.add(new UriParameterImpl().setName(name) - .setText("null".equals(literalValue) ? null : literalValue)); + parameter.setText("null".equals(literalValue) ? null : literalValue); } + parameters.add(parameter); } while (tokenizer.next(TokenKind.COMMA)); ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); return parameters; } + protected static void validateFunctionParameters(final EdmFunction function, final List<UriParameter> parameters, + final Edm edm, final EdmType referringType, final Map<String, AliasQueryOption> aliases) + throws UriParserException, UriValidationException { + for (final UriParameter parameter : parameters) { + final String parameterName = parameter.getName(); + final EdmParameter edmParameter = function.getParameter(parameterName); + final boolean isNullable = edmParameter.isNullable(); + if (parameter.getText() == null && parameter.getExpression() == null && !isNullable) { + if (parameter.getAlias() == null) { + // No alias, value is explicitly null. + throw new UriValidationException("Missing non-nullable parameter " + parameterName, + UriValidationException.MessageKeys.MISSING_PARAMETER, parameterName); + } else { + final String valueForAlias = aliases.containsKey(parameter.getAlias()) ? + parseAliasValue(parameter.getAlias(), + edmParameter.getType(), edmParameter.isNullable(), edmParameter.isCollection(), + edm, referringType, aliases).getText() : + null; + // Alias value is missing or explicitly null. + if (valueForAlias == null) { + throw new UriValidationException("Missing alias for " + parameterName, + UriValidationException.MessageKeys.MISSING_ALIAS, parameter.getAlias()); + } + } + } + } + } + + protected static AliasQueryOption parseAliasValue(final String name, final EdmType type, final boolean isNullable, + final boolean isCollection, final Edm edm, final EdmType referringType, + final Map<String, AliasQueryOption> aliases) throws UriParserException, UriValidationException { + final EdmTypeKind kind = type == null ? null : type.getKind(); + final AliasQueryOption alias = aliases.get(name); + if (alias != null && alias.getText() != null) { + UriTokenizer aliasTokenizer = new UriTokenizer(alias.getText()); + if (kind == null + || !((isCollection || kind == EdmTypeKind.COMPLEX || kind == EdmTypeKind.ENTITY ? + aliasTokenizer.next(TokenKind.jsonArrayOrObject) : + nextPrimitiveTypeValue(aliasTokenizer, (EdmPrimitiveType) type, isNullable)) + && aliasTokenizer.next(TokenKind.EOF))) { + // The alias value is not an allowed literal value, so parse it again as expression. + aliasTokenizer = new UriTokenizer(alias.getText()); + // Don't pass on the current alias to avoid circular references. + Map<String, AliasQueryOption> aliasesInner = new HashMap<String, AliasQueryOption>(aliases); + aliasesInner.remove(name); + final Expression expression = new ExpressionParser(edm, odata) + .parse(aliasTokenizer, referringType, null, aliasesInner); + final EdmType expressionType = ExpressionParser.getType(expression); + if (aliasTokenizer.next(TokenKind.EOF) + && (expressionType == null || type == null || expressionType.equals(type))) { + ((AliasQueryOptionImpl) alias).setAliasValue(expression); + } else { + throw new UriParserSemanticException("Illegal value for alias '" + alias.getName() + "'.", + UriParserSemanticException.MessageKeys.UNKNOWN_PART, alias.getText()); + } + } + } + return alias; + } + protected static List<UriParameter> parseNavigationKeyPredicate(UriTokenizer tokenizer, - final EdmNavigationProperty navigationProperty) throws UriParserException, UriValidationException { + final EdmNavigationProperty navigationProperty, + final Edm edm, final EdmType referringType, final Map<String, AliasQueryOption> aliases) + throws UriParserException, UriValidationException { if (tokenizer.next(TokenKind.OPEN)) { if (navigationProperty.isCollection()) { - return parseKeyPredicate(tokenizer, navigationProperty.getType(), navigationProperty.getPartner()); + return parseKeyPredicate(tokenizer, navigationProperty.getType(), navigationProperty.getPartner(), + edm, referringType, aliases); } else { throw new UriParserSemanticException("A key is not allowed on non-collection navigation properties.", UriParserSemanticException.MessageKeys.KEY_NOT_ALLOWED); @@ -207,7 +279,9 @@ public class ParserHelper { } protected static List<UriParameter> parseKeyPredicate(UriTokenizer tokenizer, final EdmEntityType edmEntityType, - final EdmNavigationProperty partner) throws UriParserException, UriValidationException { + final EdmNavigationProperty partner, + final Edm edm, final EdmType referringType, final Map<String, AliasQueryOption> aliases) + throws UriParserException, UriValidationException { final List<EdmKeyPropertyRef> keyPropertyRefs = edmEntityType.getKeyPropertyRefs(); if (tokenizer.next(TokenKind.CLOSE)) { throw new UriParserSemanticException( @@ -229,11 +303,11 @@ public class ParserHelper { } if (tokenizer.next(TokenKind.ODataIdentifier)) { - keys.addAll(compoundKey(tokenizer, edmEntityType)); + keys.addAll(compoundKey(tokenizer, edmEntityType, edm, referringType, aliases)); } else if (keyPropertyRefs.size() - referencedNames.size() == 1) { for (final EdmKeyPropertyRef candidate : keyPropertyRefs) { if (referencedNames.get(candidate.getName()) == null) { - keys.add(simpleKey(tokenizer, candidate)); + keys.add(simpleKey(tokenizer, candidate, edm, referringType, aliases)); break; } } @@ -271,7 +345,8 @@ public class ParserHelper { } } - private static UriParameter simpleKey(UriTokenizer tokenizer, final EdmKeyPropertyRef edmKeyPropertyRef) + private static UriParameter simpleKey(UriTokenizer tokenizer, final EdmKeyPropertyRef edmKeyPropertyRef, + final Edm edm, final EdmType referringType, final Map<String, AliasQueryOption> aliases) throws UriParserException, UriValidationException { final EdmProperty edmProperty = edmKeyPropertyRef == null ? null : edmKeyPropertyRef.getProperty(); if (nextPrimitiveTypeValue(tokenizer, @@ -279,14 +354,15 @@ public class ParserHelper { edmProperty == null ? false : edmProperty.isNullable())) { final String literalValue = tokenizer.getText(); ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); - return createUriParameter(edmProperty, edmKeyPropertyRef.getName(), literalValue); + return createUriParameter(edmProperty, edmKeyPropertyRef.getName(), literalValue, edm, referringType, aliases); } else { throw new UriParserSemanticException("The key value is not valid.", UriParserSemanticException.MessageKeys.INVALID_KEY_VALUE, edmKeyPropertyRef.getName()); } } - private static List<UriParameter> compoundKey(UriTokenizer tokenizer, final EdmEntityType edmEntityType) + private static List<UriParameter> compoundKey(UriTokenizer tokenizer, final EdmEntityType edmEntityType, + final Edm edm, final EdmType referringType, final Map<String, AliasQueryOption> aliases) throws UriParserException, UriValidationException { List<UriParameter> parameters = new ArrayList<UriParameter>(); @@ -312,7 +388,7 @@ public class ParserHelper { throw new UriValidationException("Unknown key property " + keyPredicateName, UriValidationException.MessageKeys.INVALID_KEY_PROPERTY, keyPredicateName); } - parameters.add(keyValuePair(tokenizer, keyPredicateName, edmEntityType)); + parameters.add(keyValuePair(tokenizer, keyPredicateName, edmEntityType, edm, referringType, aliases)); parameterNames.add(keyPredicateName); hasComma = tokenizer.next(TokenKind.COMMA); if (hasComma) { @@ -324,8 +400,9 @@ public class ParserHelper { return parameters; } - protected static UriParameter keyValuePair(UriTokenizer tokenizer, - final String keyPredicateName, final EdmEntityType edmEntityType) + private static UriParameter keyValuePair(UriTokenizer tokenizer, + final String keyPredicateName, final EdmEntityType edmEntityType, + final Edm edm, final EdmType referringType, final Map<String, AliasQueryOption> aliases) throws UriParserException, UriValidationException { final EdmKeyPropertyRef keyPropertyRef = edmEntityType.getKeyPropertyRef(keyPredicateName); final EdmProperty edmProperty = keyPropertyRef == null ? null : keyPropertyRef.getProperty(); @@ -338,7 +415,7 @@ public class ParserHelper { throw new UriParserSyntaxException("Key value expected.", UriParserSyntaxException.MessageKeys.SYNTAX); } if (nextPrimitiveTypeValue(tokenizer, (EdmPrimitiveType) edmProperty.getType(), edmProperty.isNullable())) { - return createUriParameter(edmProperty, keyPredicateName, tokenizer.getText()); + return createUriParameter(edmProperty, keyPredicateName, tokenizer.getText(), edm, referringType, aliases); } else { throw new UriParserSemanticException(keyPredicateName + " has not a valid key value.", UriParserSemanticException.MessageKeys.INVALID_KEY_VALUE, keyPredicateName); @@ -346,28 +423,43 @@ public class ParserHelper { } private static UriParameter createUriParameter(final EdmProperty edmProperty, final String parameterName, - final String literalValue) throws UriParserException, UriValidationException { - if (literalValue.startsWith("@")) { - return new UriParameterImpl() - .setName(parameterName) - .setAlias(literalValue); - } - + final String literalValue, final Edm edm, final EdmType referringType, + final Map<String, AliasQueryOption> aliases) throws UriParserException, UriValidationException { + final AliasQueryOption alias = literalValue.startsWith("@") ? + getKeyAlias(literalValue, edmProperty, edm, referringType, aliases) : + null; + final String value = alias == null ? literalValue : alias.getText(); final EdmPrimitiveType primitiveType = (EdmPrimitiveType) edmProperty.getType(); try { - if (!(primitiveType.validate(primitiveType.fromUriLiteral(literalValue), edmProperty.isNullable(), + if (!(primitiveType.validate(primitiveType.fromUriLiteral(value), edmProperty.isNullable(), edmProperty.getMaxLength(), edmProperty.getPrecision(), edmProperty.getScale(), edmProperty.isUnicode()))) { throw new UriValidationException("Invalid key property", UriValidationException.MessageKeys.INVALID_KEY_PROPERTY, parameterName); } } catch (final EdmPrimitiveTypeException e) { - throw new UriValidationException("Invalid key property", + throw new UriValidationException("Invalid key property", e, UriValidationException.MessageKeys.INVALID_KEY_PROPERTY, parameterName); } return new UriParameterImpl() .setName(parameterName) - .setText("null".equals(literalValue) ? null : literalValue); + .setText("null".equals(literalValue) ? null : literalValue) + .setAlias(alias == null ? null : literalValue) + .setExpression(alias == null ? null : + alias.getValue() == null ? new LiteralImpl(value, primitiveType) : alias.getValue()); + } + + private static AliasQueryOption getKeyAlias(final String name, final EdmProperty edmProperty, + final Edm edm, final EdmType referringType, final Map<String, AliasQueryOption> aliases) + throws UriParserException, UriValidationException { + if (aliases.containsKey(name)) { + return parseAliasValue(name, + edmProperty.getType(), edmProperty.isNullable(), edmProperty.isCollection(), + edm, referringType, aliases); + } else { + throw new UriValidationException("Alias '" + name + "' for key value not found.", + UriValidationException.MessageKeys.MISSING_ALIAS, name); + } } private static boolean nextPrimitiveTypeValue(UriTokenizer tokenizer, http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/110c7b0e/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java index 87cb91a..8d6d52d 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java @@ -18,7 +18,9 @@ */ package org.apache.olingo.server.core.uri.parser; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.apache.olingo.commons.api.edm.Edm; import org.apache.olingo.commons.api.edm.EdmAction; @@ -35,11 +37,10 @@ import org.apache.olingo.commons.api.edm.EdmStructuredType; import org.apache.olingo.commons.api.edm.EdmType; import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.edm.constants.EdmTypeKind; -import org.apache.olingo.server.api.uri.UriInfoKind; import org.apache.olingo.server.api.uri.UriParameter; import org.apache.olingo.server.api.uri.UriResource; import org.apache.olingo.server.api.uri.UriResourcePartTyped; -import org.apache.olingo.server.core.uri.UriInfoImpl; +import org.apache.olingo.server.api.uri.queryoption.AliasQueryOption; import org.apache.olingo.server.core.uri.UriResourceActionImpl; import org.apache.olingo.server.core.uri.UriResourceComplexPropertyImpl; import org.apache.olingo.server.core.uri.UriResourceCountImpl; @@ -59,10 +60,12 @@ public class ResourcePathParser { private final Edm edm; private final EdmEntityContainer edmEntityContainer; + private final Map<String, AliasQueryOption> aliases; private UriTokenizer tokenizer; - public ResourcePathParser(final Edm edm) { + public ResourcePathParser(final Edm edm, final Map<String, AliasQueryOption> aliases) { this.edm = edm; + this.aliases = aliases; edmEntityContainer = edm.getEntityContainer(); } @@ -100,8 +103,7 @@ public class ResourcePathParser { UriParserSyntaxException.MessageKeys.SYNTAX); } - public UriInfoImpl parseDollarEntityTypeCast(final String pathSegment) throws UriParserException { - UriInfoImpl uriInfo = new UriInfoImpl().setKind(UriInfoKind.entityId); + public EdmEntityType parseDollarEntityTypeCast(final String pathSegment) throws UriParserException { tokenizer = new UriTokenizer(pathSegment); ParserHelper.requireNext(tokenizer, TokenKind.QualifiedName); final String name = tokenizer.getText(); @@ -110,18 +112,16 @@ public class ResourcePathParser { if (type == null) { throw new UriParserSemanticException("Type '" + name + "' not found.", UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, name); - } else { - uriInfo.setEntityTypeCast(type); } - return uriInfo; + return type; } - public UriInfoImpl parseCrossjoinSegment(final String pathSegment) throws UriParserException { - UriInfoImpl uriInfo = new UriInfoImpl().setKind(UriInfoKind.crossjoin); + public List<String> parseCrossjoinSegment(final String pathSegment) throws UriParserException { tokenizer = new UriTokenizer(pathSegment); ParserHelper.requireNext(tokenizer, TokenKind.CROSSJOIN); ParserHelper.requireNext(tokenizer, TokenKind.OPEN); // At least one entity-set name is mandatory. Try to fetch all. + List<String> entitySetNames = new ArrayList<String>(); do { ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier); final String name = tokenizer.getText(); @@ -130,12 +130,12 @@ public class ResourcePathParser { throw new UriParserSemanticException("Expected Entity Set Name.", UriParserSemanticException.MessageKeys.UNKNOWN_PART, name); } else { - uriInfo.addEntitySetName(name); + entitySetNames.add(name); } } while (tokenizer.next(TokenKind.COMMA)); ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); ParserHelper.requireTokenEnd(tokenizer); - return uriInfo; + return entitySetNames; } private UriResource ref(final UriResource previous) throws UriParserException { @@ -180,7 +180,7 @@ public class ResourcePathParser { if (tokenizer.next(TokenKind.OPEN)) { final List<UriParameter> keyPredicates = - ParserHelper.parseKeyPredicate(tokenizer, entitySetResource.getEntityType(), null); + ParserHelper.parseKeyPredicate(tokenizer, entitySetResource.getEntityType(), null, edm, null, aliases); entitySetResource.setKeyPredicates(keyPredicates); } @@ -251,7 +251,8 @@ public class ResourcePathParser { UriParserSemanticException.MessageKeys.PROPERTY_NOT_IN_TYPE, structType.getFullQualifiedName().getFullQualifiedNameAsString(), name); } - List<UriParameter> keyPredicate = ParserHelper.parseNavigationKeyPredicate(tokenizer, navigationProperty); + List<UriParameter> keyPredicate = + ParserHelper.parseNavigationKeyPredicate(tokenizer, navigationProperty, edm, null, aliases); ParserHelper.requireTokenEnd(tokenizer); return new UriResourceNavigationPropertyImpl(navigationProperty) .setKeyPredicates(keyPredicate); @@ -320,7 +321,8 @@ public class ResourcePathParser { ((UriResourceWithKeysImpl) previousTyped).setEntryTypeFilter(type); } if (tokenizer.next(TokenKind.OPEN)) { - final List<UriParameter> keys = ParserHelper.parseKeyPredicate(tokenizer, (EdmEntityType) type, null); + final List<UriParameter> keys = + ParserHelper.parseKeyPredicate(tokenizer, (EdmEntityType) type, null, edm, null, aliases); if (previousTyped.isCollection()) { ((UriResourceWithKeysImpl) previousTyped).setKeyPredicates(keys); } else { @@ -359,7 +361,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, edm, null, false); + final List<UriParameter> parameters = ParserHelper.parseFunctionParameters(tokenizer, edm, null, false, aliases); final List<String> names = ParserHelper.getParameterNames(parameters); EdmFunction function = null; if (edmFunctionImport != null) { @@ -379,13 +381,15 @@ public class ResourcePathParser { UriParserSemanticException.MessageKeys.UNKNOWN_PART, boundFunctionName.getFullQualifiedNameAsString()); } } + ParserHelper.validateFunctionParameters(function, parameters, edm, null, aliases); UriResourceFunctionImpl resource = new UriResourceFunctionImpl(edmFunctionImport, function, parameters); if (tokenizer.next(TokenKind.OPEN)) { if (function.getReturnType() != null && function.getReturnType().getType().getKind() == EdmTypeKind.ENTITY && function.getReturnType().isCollection()) { resource.setKeyPredicates( - ParserHelper.parseKeyPredicate(tokenizer, (EdmEntityType) function.getReturnType().getType(), null)); + ParserHelper.parseKeyPredicate(tokenizer, + (EdmEntityType) function.getReturnType().getType(), null, edm, null, aliases)); } else { throw new UriParserSemanticException("A key is not allowed.", UriParserSemanticException.MessageKeys.KEY_NOT_ALLOWED); http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/110c7b0e/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/SelectParser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/SelectParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/SelectParser.java index 00f3673..9ec29e3 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/SelectParser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/SelectParser.java @@ -123,11 +123,17 @@ public class SelectParser { } private FullQualifiedName parseAllOperationsInSchema(UriTokenizer tokenizer) throws UriParserException { - final String name = tokenizer.getText(); + final String namespace = tokenizer.getText(); if (tokenizer.next(TokenKind.DOT)) { if (tokenizer.next(TokenKind.STAR)) { - // TODO: Validate the namespace without loading the whole schema. - return new FullQualifiedName(name, tokenizer.getText()); + // Validate the namespace. Currently a namespace from a non-default schema is not supported. + // There is no direct access to the namespace without loading the whole schema; + // however, the default entity container should always be there, so its access methods can be used. + if (edm.getEntityContainer(new FullQualifiedName(namespace, edm.getEntityContainer().getName())) == null) { + throw new UriParserSemanticException("Wrong namespace '" + namespace + "'.", + UriParserSemanticException.MessageKeys.UNKNOWN_PART, namespace); + } + return new FullQualifiedName(namespace, tokenizer.getText()); } else { throw new UriParserSemanticException("Expected star after dot.", UriParserSemanticException.MessageKeys.UNKNOWN_PART, ""); http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/110c7b0e/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java index 0504473..d218666 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java @@ -1311,8 +1311,18 @@ public class UriTokenizer { || nextJsonArrayOrObject(); } + /** + * Moves past a JSON object member if found; otherwise leaves the index unchanged. + * @return whether a JSON object member has been found at the current index + */ private boolean nextJsonMember() { - return nextJsonString() && nextCharacter(':') && nextJsonValue(); + final int lastGoodIndex = index; + if (nextJsonString() && nextCharacter(':') && nextJsonValue()) { + return true; + } else { + index = lastGoodIndex; + return false; + } } /**
