From 927ecb93e34a8a1d4b5ff1dfefb96882b50a028b Mon Sep 17 00:00:00 2001 From: Klaus Straubinger Date: Fri, 4 Dec 2015 15:47:51 +0100 Subject: [PATCH 1/2] [OLINGO-834] URI resource-path parser in Java Signed-off-by: Michael Bolz --- .../olingo/fit/tecsvc/client/BasicITCase.java | 2 +- .../core/edm/primitivetype/EdmBinary.java | 12 +- .../edm/provider/EdmActionImportImplTest.java | 2 +- .../edm/provider/EdmProviderImplTest.java | 4 +- .../core/edm/provider/EdmSchemaImplTest.java | 1 + .../olingo/server/core/ErrorHandler.java | 8 +- .../olingo/server/core/ServiceDispatcher.java | 4 +- .../olingo/server/core/ServiceRequest.java | 6 +- .../olingo/server/core/ODataHandler.java | 4 +- .../olingo/server/core/uri/UriHelperImpl.java | 9 +- .../UriResourceNavigationPropertyImpl.java | 5 +- .../core/uri/UriResourceWithKeysImpl.java | 3 +- .../olingo/server/core/uri/parser/Parser.java | 202 +++-- .../core/uri/parser/ResourcePathParser.java | 692 ++++++++++++++++++ .../core/uri/parser/UriParseTreeVisitor.java | 2 +- .../server/core/uri/parser/UriTokenizer.java | 592 +++++++++++++++ .../core/uri/validator/UriValidator.java | 74 +- .../core/uri/parser/UriTokenizerTest.java | 369 ++++++++++ .../parser/search/SearchTokenizerTest.java | 1 - .../server/tecsvc/data/DataProviderTest.java | 4 +- .../core/PreconditionsValidatorTest.java | 25 +- .../utils/ContextURLHelperTest.java | 4 +- .../olingo/server/core/uri/UriHelperTest.java | 4 +- .../server/core/uri/UriResourceImplTest.java | 14 +- .../core/uri/antlr/TestFullResourcePath.java | 154 ++-- .../core/uri/antlr/TestUriParserImpl.java | 15 +- .../server/core/uri/parser/ParserTest.java | 4 +- .../core/uri/testutil/FilterValidator.java | 62 +- .../core/uri/testutil/ParserWithLogging.java | 5 +- .../core/uri/testutil/ResourceValidator.java | 13 +- .../core/uri/testutil/TestUriValidator.java | 14 +- .../core/uri/validator/UriValidatorTest.java | 17 +- 32 files changed, 1973 insertions(+), 354 deletions(-) create mode 100644 lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java create mode 100644 lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java create mode 100644 lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java diff --git a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BasicITCase.java b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BasicITCase.java index a09cafa5a..c8e4848b8 100644 --- a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BasicITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BasicITCase.java @@ -245,7 +245,7 @@ public class BasicITCase extends AbstractParamTecSvcITCase { } catch (final ODataClientErrorException e) { assertEquals(HttpStatusCode.BAD_REQUEST.getStatusCode(), e.getStatusLine().getStatusCode()); final ODataError error = e.getODataError(); - assertThat(error.getMessage(), containsString("key property")); + assertThat(error.getMessage(), containsString("key")); } } diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/primitivetype/EdmBinary.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/primitivetype/EdmBinary.java index 735250a7b..bdbc3fadc 100644 --- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/primitivetype/EdmBinary.java +++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/primitivetype/EdmBinary.java @@ -133,19 +133,19 @@ public class EdmBinary extends SingletonPrimitiveType { final Boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale, final Boolean isUnicode) { - return value == null - ? isNullable == null || isNullable - : isBase64(value.getBytes(UTF_8)) && validateMaxLength(value, maxLength); + return value == null ? + isNullable == null || isNullable : + isBase64(value.getBytes(UTF_8)) && validateMaxLength(value, maxLength); } private static boolean validateMaxLength(final String value, final Integer maxLength) { - return maxLength == null ? true - : // Every three bytes are represented as four base-64 characters. + return maxLength == null ? true : + // Every three bytes are represented as four base-64 characters. // Additionally, there could be up to two padding "=" characters // if the number of bytes is not a multiple of three, // and there could be line feeds, possibly with carriage returns. maxLength >= (value.length() - lineEndingsLength(value)) * 3 / 4 - - (value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0); + - (value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0); } private static int lineEndingsLength(final String value) { diff --git a/lib/commons-core/src/test/java/org/apache/olingo/server/core/edm/provider/EdmActionImportImplTest.java b/lib/commons-core/src/test/java/org/apache/olingo/server/core/edm/provider/EdmActionImportImplTest.java index 02824f433..e4cd5930c 100644 --- a/lib/commons-core/src/test/java/org/apache/olingo/server/core/edm/provider/EdmActionImportImplTest.java +++ b/lib/commons-core/src/test/java/org/apache/olingo/server/core/edm/provider/EdmActionImportImplTest.java @@ -96,7 +96,7 @@ public class EdmActionImportImplTest { String target = "nonExisting"; CsdlActionImport providerActionImport = new CsdlActionImport().setName("actionImportName").setEntitySet(target); EdmProviderImpl edm = mock(EdmProviderImpl.class); - when(edm.getEntityContainer(null)).thenReturn(container); + when(edm.getEntityContainer()).thenReturn(container); EdmActionImport actionImport = new EdmActionImportImpl(edm, container, providerActionImport); actionImport.getReturnedEntitySet(); } diff --git a/lib/commons-core/src/test/java/org/apache/olingo/server/core/edm/provider/EdmProviderImplTest.java b/lib/commons-core/src/test/java/org/apache/olingo/server/core/edm/provider/EdmProviderImplTest.java index 6ce6ab85e..7ba529a3d 100644 --- a/lib/commons-core/src/test/java/org/apache/olingo/server/core/edm/provider/EdmProviderImplTest.java +++ b/lib/commons-core/src/test/java/org/apache/olingo/server/core/edm/provider/EdmProviderImplTest.java @@ -183,7 +183,7 @@ public class EdmProviderImplTest { when(localProvider.getAliasInfos()).thenThrow(new ODataException("msg")); Edm localEdm = new EdmProviderImpl(localProvider); - localEdm.getEntityContainer(null); + localEdm.getEntityContainer(); } @Test @@ -193,7 +193,7 @@ public class EdmProviderImplTest { assertEquals(FQN.getNamespace(), entityContainer.getNamespace()); assertEquals(FQN.getName(), entityContainer.getName()); - entityContainer = edm.getEntityContainer(null); + entityContainer = edm.getEntityContainer(); assertNotNull(entityContainer); assertEquals(FQN.getNamespace(), entityContainer.getNamespace()); assertEquals(FQN.getName(), entityContainer.getName()); diff --git a/lib/commons-core/src/test/java/org/apache/olingo/server/core/edm/provider/EdmSchemaImplTest.java b/lib/commons-core/src/test/java/org/apache/olingo/server/core/edm/provider/EdmSchemaImplTest.java index e6502af1c..2c95ce1a1 100644 --- a/lib/commons-core/src/test/java/org/apache/olingo/server/core/edm/provider/EdmSchemaImplTest.java +++ b/lib/commons-core/src/test/java/org/apache/olingo/server/core/edm/provider/EdmSchemaImplTest.java @@ -229,6 +229,7 @@ public class EdmSchemaImplTest { assertTrue(container == edm.getEntityContainer(new FullQualifiedName(schema.getNamespace(), container.getName()))); assertTrue(container == edm.getEntityContainer(null)); + assertTrue(container == edm.getEntityContainer()); } private class LocalProvider implements CsdlEdmProvider { diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ErrorHandler.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ErrorHandler.java index 5f425da07..33f65cd65 100644 --- a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ErrorHandler.java +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ErrorHandler.java @@ -90,13 +90,15 @@ public class ErrorHandler { final ODataServerError serverError) { ContentType requestedContentType; try { - UriInfo uriInfo = new Parser().parseUri(request.getRawODataPath(), request.getRawQueryPath(), - null, this.metadata.getEdm()); + final UriInfo uriInfo = new Parser(metadata.getEdm(), odata) + .parseUri(request.getRawODataPath(), request.getRawQueryPath(), null); requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, this.customContent, RepresentationType.ERROR); } catch (final ContentNegotiatorException e) { requestedContentType = ContentType.JSON; - } catch (UriParserException e) { + } catch (final UriParserException e) { + requestedContentType = ContentType.JSON; + } catch (final UriValidationException e) { requestedContentType = ContentType.JSON; } processError(response, serverError, requestedContentType); diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceDispatcher.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceDispatcher.java index 17ec3581a..45de7576f 100644 --- a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceDispatcher.java +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceDispatcher.java @@ -76,8 +76,8 @@ public class ServiceDispatcher extends RequestURLHierarchyVisitor { public void execute(ODataRequest odRequest, ODataResponse odResponse) throws ODataLibraryException, ODataApplicationException { - UriInfo uriInfo = new Parser().parseUri(odRequest.getRawODataPath(), odRequest.getRawQueryPath(), null, - this.metadata.getEdm()); + UriInfo uriInfo = new Parser(this.metadata.getEdm(), odata) + .parseUri(odRequest.getRawODataPath(), odRequest.getRawQueryPath(), null); new UriValidator().validate(uriInfo, odRequest.getMethod()); diff --git a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceRequest.java b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceRequest.java index 8195b85d6..0796144ea 100644 --- a/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceRequest.java +++ b/lib/server-core-ext/src/main/java/org/apache/olingo/server/core/ServiceRequest.java @@ -47,6 +47,7 @@ import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.core.requests.DataRequest; 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.validator.UriValidationException; public abstract class ServiceRequest { protected OData odata; @@ -240,7 +241,7 @@ public abstract class ServiceRequest { return null; } - public DataRequest parseLink(URI uri) throws UriParserException, URISyntaxException { + public DataRequest parseLink(URI uri) throws UriParserException, UriValidationException, URISyntaxException { String path = "/"; URI servicePath = new URI(getODataRequest().getRawBaseUri()); path = servicePath.getPath(); @@ -253,8 +254,7 @@ public abstract class ServiceRequest { rawPath = rawPath.substring(e+path.length()); } - UriInfo uriInfo = new Parser().parseUri(rawPath, uri.getQuery(), null, - this.serviceMetadata.getEdm()); + UriInfo uriInfo = new Parser(serviceMetadata.getEdm(), odata).parseUri(rawPath, uri.getQuery(), null); ServiceDispatcher dispatcher = new ServiceDispatcher(odata, serviceMetadata, null, customContentType); dispatcher.visit(uriInfo); return (DataRequest)dispatcher.request; 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 12f2dfd99..8c2e286c9 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 @@ -129,8 +129,8 @@ public class ODataHandler { final int measurementUriParser = debugger.startRuntimeMeasurement("UriParser", "parseUri"); try { - uriInfo = new Parser().parseUri(request.getRawODataPath(), request.getRawQueryPath(), null, - serviceMetadata.getEdm()); + uriInfo = new Parser(serviceMetadata.getEdm(), odata) + .parseUri(request.getRawODataPath(), request.getRawQueryPath(), null); } catch (final ODataLibraryException e) { debugger.stopRuntimeMeasurement(measurementUriParser); debugger.stopRuntimeMeasurement(measurementHandle); diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriHelperImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriHelperImpl.java index c463765d0..3bc5ad0af 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriHelperImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriHelperImpl.java @@ -29,6 +29,7 @@ import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; import org.apache.olingo.commons.api.edm.EdmProperty; import org.apache.olingo.commons.api.edm.EdmStructuredType; import org.apache.olingo.commons.core.Encoder; +import org.apache.olingo.server.api.ODataLibraryException; import org.apache.olingo.server.api.deserializer.DeserializerException; import org.apache.olingo.server.api.deserializer.DeserializerException.MessageKeys; import org.apache.olingo.server.api.serializer.SerializerException; @@ -39,9 +40,9 @@ import org.apache.olingo.server.api.uri.UriResourceEntitySet; import org.apache.olingo.server.api.uri.UriResourceKind; import org.apache.olingo.server.api.uri.queryoption.ExpandOption; import org.apache.olingo.server.api.uri.queryoption.SelectOption; +import org.apache.olingo.server.core.ODataImpl; import org.apache.olingo.server.core.serializer.utils.ContextURLHelper; import org.apache.olingo.server.core.uri.parser.Parser; -import org.apache.olingo.server.core.uri.parser.UriParserException; public class UriHelperImpl implements UriHelper { @@ -107,8 +108,8 @@ public class UriHelperImpl implements UriHelper { oDataPath = oDataPath.startsWith("/") ? oDataPath : "/" + oDataPath; try { - final List uriResourceParts = new Parser().parseUri(oDataPath, null, null, edm) - .getUriResourceParts(); + final List uriResourceParts = + new Parser(edm, new ODataImpl()).parseUri(oDataPath, null, null).getUriResourceParts(); if (uriResourceParts.size() == 1 && uriResourceParts.get(0).getKind() == UriResourceKind.entitySet) { final UriResourceEntitySet entityUriResource = (UriResourceEntitySet) uriResourceParts.get(0); @@ -117,7 +118,7 @@ public class UriHelperImpl implements UriHelper { throw new DeserializerException("Invalid entity binding link", MessageKeys.INVALID_ENTITY_BINDING_LINK, entityId); - } catch (UriParserException e) { + } catch (final ODataLibraryException e) { throw new DeserializerException("Invalid entity binding link", e, MessageKeys.INVALID_ENTITY_BINDING_LINK, entityId); } diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriResourceNavigationPropertyImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriResourceNavigationPropertyImpl.java index 1a4fa444c..f4390b556 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriResourceNavigationPropertyImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriResourceNavigationPropertyImpl.java @@ -49,10 +49,7 @@ public class UriResourceNavigationPropertyImpl extends UriResourceWithKeysImpl i @Override public boolean isCollection() { - if (keyPredicates != null) { - return false; - } - return navigationProperty.isCollection(); + return navigationProperty.isCollection() && keyPredicates == null; } @Override diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriResourceWithKeysImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriResourceWithKeysImpl.java index 54d62a1d6..c300d78c2 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriResourceWithKeysImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/UriResourceWithKeysImpl.java @@ -18,7 +18,6 @@ */ package org.apache.olingo.server.core.uri; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -48,7 +47,7 @@ public abstract class UriResourceWithKeysImpl extends UriResourceImpl implements public List getKeyPredicates() { return keyPredicates == null ? Collections. emptyList() : - new ArrayList(keyPredicates); + Collections.unmodifiableList(keyPredicates); } public UriResourceWithKeysImpl setKeyPredicates(final List list) { 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 d12b85370..5caaaeb82 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java @@ -18,7 +18,6 @@ */ package org.apache.olingo.server.core.uri.parser; -import java.util.ArrayList; import java.util.List; import org.antlr.v4.runtime.ANTLRInputStream; @@ -31,11 +30,15 @@ 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.edm.Edm; +import org.apache.olingo.commons.api.edm.EdmEntityContainer; import org.apache.olingo.commons.api.ex.ODataRuntimeException; +import org.apache.olingo.server.api.OData; 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.UriResourceAction; import org.apache.olingo.server.api.uri.UriResourceCount; +import org.apache.olingo.server.api.uri.UriResourceFunction; import org.apache.olingo.server.api.uri.UriResourcePartTyped; import org.apache.olingo.server.api.uri.UriResourceRef; import org.apache.olingo.server.api.uri.UriResourceValue; @@ -46,18 +49,14 @@ 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.antlr.UriLexer; import org.apache.olingo.server.core.uri.antlr.UriParserParser; -import org.apache.olingo.server.core.uri.antlr.UriParserParser.AllEOFContext; -import org.apache.olingo.server.core.uri.antlr.UriParserParser.BatchEOFContext; -import org.apache.olingo.server.core.uri.antlr.UriParserParser.CrossjoinEOFContext; -import org.apache.olingo.server.core.uri.antlr.UriParserParser.EntityEOFContext; import org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandItemsEOFContext; import org.apache.olingo.server.core.uri.antlr.UriParserParser.FilterExpressionEOFContext; -import org.apache.olingo.server.core.uri.antlr.UriParserParser.MetadataEOFContext; import org.apache.olingo.server.core.uri.antlr.UriParserParser.OrderByEOFContext; -import org.apache.olingo.server.core.uri.antlr.UriParserParser.PathSegmentEOFContext; import org.apache.olingo.server.core.uri.antlr.UriParserParser.SelectEOFContext; +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; @@ -71,6 +70,7 @@ 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.validator.UriValidationException; public class Parser { private static final String ATOM = "atom"; @@ -78,19 +78,22 @@ public class Parser { private static final String XML = "xml"; private static final String AT = "@"; private static final String NULL = "null"; + int logLevel = 0; + private final Edm edm; + private final OData odata; private enum ParserEntryRules { - All, Batch, CrossJoin, Entity, ExpandItems, FilterExpression, Metadata, PathSegment, Orderby, Select, Search + ExpandItems, FilterExpression, Orderby, Select } - public Parser setLogLevel(final int logLevel) { - this.logLevel = logLevel; - return this; + public Parser(final Edm edm, final OData odata) { + this.edm = edm; + this.odata = odata; } - public UriInfo parseUri(final String path, final String query, final String fragment, final Edm edm) - throws UriParserException { + public UriInfo parseUri(final String path, final String query, final String fragment) + throws UriParserException, UriValidationException { UriContext context = new UriContext(); UriParseTreeVisitor uriParseTreeVisitor = new UriParseTreeVisitor(edm, context); @@ -104,65 +107,74 @@ public class Parser { if (firstSegment.isEmpty()) { ensureLastSegment(firstSegment, 0, uri.pathSegmentListDecoded.size()); context.contextUriInfo = new UriInfoImpl().setKind(UriInfoKind.service); - } else if (firstSegment.startsWith("$batch")) { + + } else if (firstSegment.equals("$batch")) { ensureLastSegment(firstSegment, 1, uri.pathSegmentListDecoded.size()); - BatchEOFContext ctxBatchEOF = - (BatchEOFContext) parseRule(uri.pathSegmentListDecoded.get(0), ParserEntryRules.Batch); + context.contextUriInfo = new UriInfoImpl().setKind(UriInfoKind.batch); - uriParseTreeVisitor.visitBatchEOF(ctxBatchEOF); - } else if (firstSegment.startsWith("$metadata")) { + } else if (firstSegment.equals("$metadata")) { ensureLastSegment(firstSegment, 1, uri.pathSegmentListDecoded.size()); - MetadataEOFContext ctxMetadataEOF = - (MetadataEOFContext) parseRule(uri.pathSegmentListDecoded.get(0), ParserEntryRules.Metadata); - - uriParseTreeVisitor.visitMetadataEOF(ctxMetadataEOF); - + context.contextUriInfo = new UriInfoImpl().setKind(UriInfoKind.metadata); context.contextUriInfo.setFragment(uri.fragment); - } else if (firstSegment.startsWith("$entity")) { - context.contextUriInfo = new UriInfoImpl().setKind(UriInfoKind.entityId); + } else if (firstSegment.equals("$all")) { + ensureLastSegment(firstSegment, 1, uri.pathSegmentListDecoded.size()); + context.contextUriInfo = new UriInfoImpl().setKind(UriInfoKind.all); + + } else if (firstSegment.equals("$entity")) { if (uri.pathSegmentListDecoded.size() > 1) { final String typeCastSegment = uri.pathSegmentListDecoded.get(1); ensureLastSegment(typeCastSegment, 2, uri.pathSegmentListDecoded.size()); - EntityEOFContext ctxEntityEOF = - (EntityEOFContext) parseRule(typeCastSegment, ParserEntryRules.Entity); - uriParseTreeVisitor.visitEntityEOF(ctxEntityEOF); + context.contextUriInfo = new ResourcePathParser(edm, odata).parseDollarEntityTypeCast(typeCastSegment); + context.contextTypes.push( + uriParseTreeVisitor.new TypeInformation(context.contextUriInfo.getEntityTypeCast(), false)); + } else { + context.contextUriInfo = new UriInfoImpl().setKind(UriInfoKind.entityId); } - } else if (firstSegment.startsWith("$all")) { - ensureLastSegment(firstSegment, 1, uri.pathSegmentListDecoded.size()); - AllEOFContext ctxResourcePathEOF = - (AllEOFContext) parseRule(uri.pathSegmentListDecoded.get(0), ParserEntryRules.All); - - uriParseTreeVisitor.visitAllEOF(ctxResourcePathEOF); } else if (firstSegment.startsWith("$crossjoin")) { ensureLastSegment(firstSegment, 1, uri.pathSegmentListDecoded.size()); - CrossjoinEOFContext ctxResourcePathEOF = - (CrossjoinEOFContext) parseRule(uri.pathSegmentListDecoded.get(0), ParserEntryRules.CrossJoin); + context.contextUriInfo = new ResourcePathParser(edm, odata) + .parseCrossjoinSegment(uri.pathSegmentListDecoded.get(0)); + final EdmEntityContainer container = edm.getEntityContainer(); + for (final String name : context.contextUriInfo.getEntitySetNames()) { + context.contextTypes.push( + uriParseTreeVisitor.new TypeInformation(container.getEntitySet(name).getEntityType(), true)); + } - uriParseTreeVisitor.visitCrossjoinEOF(ctxResourcePathEOF); } else { - List ctxPathSegments = new ArrayList(); - for (String pathSegment : uri.pathSegmentListDecoded) { - PathSegmentEOFContext ctxPathSegment = - (PathSegmentEOFContext) parseRule(pathSegment, ParserEntryRules.PathSegment); - ctxPathSegments.add(ctxPathSegment); - } - context.contextUriInfo = new UriInfoImpl().setKind(UriInfoKind.resource); - - for (PathSegmentEOFContext ctxPathSegment : ctxPathSegments) { - // add checks for batch, entity, metadata, all, crossjoin - uriParseTreeVisitor.visitPathSegmentEOF(ctxPathSegment); + final ResourcePathParser resourcePathParser = new ResourcePathParser(edm, odata); + int count = 0; + UriResource lastSegment = null; + for (final String pathSegment : uri.pathSegmentListDecoded) { + count++; + final UriResource segment = resourcePathParser.parsePathSegment(pathSegment, lastSegment); + if (segment != null) { + if (segment instanceof UriResourceCount + || segment instanceof UriResourceRef + || segment instanceof UriResourceValue) { + ensureLastSegment(pathSegment, count, uri.pathSegmentListDecoded.size()); + } else if (segment instanceof UriResourceAction + || segment instanceof UriResourceFunction + && !((UriResourceFunction) segment).getFunction().isComposable()) { + if (count < uri.pathSegmentListDecoded.size()) { + throw new UriValidationException( + "The segment of an action or of a non-composable function must be the last resource-path segment.", + UriValidationException.MessageKeys.UNALLOWED_RESOURCE_PATH, + uri.pathSegmentListDecoded.get(count)); + } + lastSegment = segment; + } else if (segment instanceof UriResourceStartingTypeFilterImpl) { + throw new UriParserSemanticException("First resource-path segment must not be namespace-qualified.", + UriParserSemanticException.MessageKeys.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT); + } else { + lastSegment = segment; + } + context.contextUriInfo.addResourcePart(segment); + } } - UriResource lastSegment = context.contextUriInfo.getLastResourcePart(); - if (lastSegment instanceof UriResourceCount - || lastSegment instanceof UriResourceRef - || lastSegment instanceof UriResourceValue) { - final List parts = context.contextUriInfo.getUriResourceParts(); - lastSegment = parts.get(parts.size() - 2); - } if (lastSegment instanceof UriResourcePartTyped) { UriResourcePartTyped typed = (UriResourcePartTyped) lastSegment; @@ -174,13 +186,12 @@ public class Parser { } // second, read the system query options and the custom query options - for (RawUri.QueryOption option : uri.queryOptionListDecoded) { + for (final RawUri.QueryOption option : uri.queryOptionListDecoded) { if (option.name.startsWith("$")) { SystemQueryOption systemOption = null; if (option.name.equals(SystemQueryOptionKind.FILTER.toString())) { FilterExpressionEOFContext ctxFilterExpression = (FilterExpressionEOFContext) parseRule(option.value, ParserEntryRules.FilterExpression); - systemOption = (FilterOptionImpl) uriParseTreeVisitor.visitFilterExpressionEOF(ctxFilterExpression); } else if (option.name.equals(SystemQueryOptionKind.FORMAT.toString())) { @@ -201,7 +212,6 @@ public class Parser { } else if (option.name.equals(SystemQueryOptionKind.EXPAND.toString())) { ExpandItemsEOFContext ctxExpandItems = (ExpandItemsEOFContext) parseRule(option.value, ParserEntryRules.ExpandItems); - systemOption = (ExpandOptionImpl) uriParseTreeVisitor.visitExpandItemsEOF(ctxExpandItems); } else if (option.name.equals(SystemQueryOptionKind.ID.toString())) { @@ -210,22 +220,24 @@ public class Parser { idOption.setText(option.value); idOption.setValue(option.value); systemOption = idOption; + } else if (option.name.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 (option.name.equals(SystemQueryOptionKind.ORDERBY.toString())) { OrderByEOFContext ctxOrderByExpression = (OrderByEOFContext) parseRule(option.value, ParserEntryRules.Orderby); - systemOption = (OrderByOptionImpl) uriParseTreeVisitor.visitOrderByEOF(ctxOrderByExpression); + } else if (option.name.equals(SystemQueryOptionKind.SEARCH.toString())) { - SearchParser searchParser = new SearchParser(); - systemOption = searchParser.parse(option.value); + systemOption = new SearchParser().parse(option.value); + } else if (option.name.equals(SystemQueryOptionKind.SELECT.toString())) { SelectEOFContext ctxSelectEOF = (SelectEOFContext) parseRule(option.value, ParserEntryRules.Select); - systemOption = (SelectOptionImpl) uriParseTreeVisitor.visitSelectEOF(ctxSelectEOF); + } else if (option.name.equals(SystemQueryOptionKind.SKIP.toString())) { SkipOptionImpl skipOption = new SkipOptionImpl(); skipOption.setName(option.name); @@ -238,12 +250,14 @@ public class Parser { option.name, option.value); } systemOption = skipOption; + } else if (option.name.equals(SystemQueryOptionKind.SKIPTOKEN.toString())) { SkipTokenOptionImpl skipTokenOption = new SkipTokenOptionImpl(); skipTokenOption.setName(option.name); skipTokenOption.setText(option.value); skipTokenOption.setValue(option.value); systemOption = skipTokenOption; + } else if (option.name.equals(SystemQueryOptionKind.TOP.toString())) { TopOptionImpl topOption = new TopOptionImpl(); topOption.setName(option.name); @@ -256,6 +270,7 @@ public class Parser { option.name, option.value); } systemOption = topOption; + } else if (option.name.equals(SystemQueryOptionKind.COUNT.toString())) { CountOptionImpl inlineCountOption = new CountOptionImpl(); inlineCountOption.setName(option.name); @@ -268,6 +283,7 @@ public class Parser { option.name, option.value); } systemOption = inlineCountOption; + } else { throw new UriParserSyntaxException("Unknown system query option!", UriParserSyntaxException.MessageKeys.UNKNOWN_SYSTEM_QUERY_OPTION, option.name); @@ -278,12 +294,23 @@ public class Parser { throw new UriParserSyntaxException("Double system query option!", e, UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, option.name); } + } else if (option.name.startsWith(AT)) { if (context.contextUriInfo.getAlias(option.name) == null) { - final FilterExpressionEOFContext filterExpCtx = - (FilterExpressionEOFContext) parseRule(option.value, ParserEntryRules.FilterExpression); - final Expression expression = ((FilterOption) uriParseTreeVisitor.visitFilterExpressionEOF(filterExpCtx)) - .getExpression(); + // TODO: Create a proper alias-value parser that can parse also common expressions. + Expression expression = null; + if (!option.value.isEmpty() && (option.value.charAt(0) == '[' || option.value.charAt(0) == '{')) { + UriTokenizer tokenizer = new UriTokenizer(option.value); + if (!(tokenizer.next(TokenKind.jsonArrayOrObject) && tokenizer.next(TokenKind.EOF))) { + throw new UriParserSyntaxException("Illegal value for alias '" + option.name + "'.", + UriParserSyntaxException.MessageKeys.SYNTAX); + } + } else { + final FilterExpressionEOFContext filterExpCtx = + (FilterExpressionEOFContext) parseRule(option.value, ParserEntryRules.FilterExpression); + expression = ((FilterOption) uriParseTreeVisitor.visitFilterExpressionEOF(filterExpCtx)) + .getExpression(); + } context.contextUriInfo.addAlias((AliasQueryOption) new AliasQueryOptionImpl() .setAliasValue(expression) .setName(option.name) @@ -292,6 +319,7 @@ public class Parser { throw new UriParserSyntaxException("Alias already specified! Name: " + option.name, UriParserSyntaxException.MessageKeys.DUPLICATED_ALIAS, option.name); } + } else { context.contextUriInfo.addCustomQueryOption((CustomQueryOption) new CustomQueryOptionImpl() @@ -354,21 +382,6 @@ public class Parser { // parse switch (entryPoint) { - case All: - ret = parser.allEOF(); - break; - case Batch: - ret = parser.batchEOF(); - break; - case CrossJoin: - ret = parser.crossjoinEOF(); - break; - case Metadata: - ret = parser.metadataEOF(); - break; - case PathSegment: - ret = parser.pathSegmentEOF(); - break; case FilterExpression: lexer.mode(Lexer.DEFAULT_MODE); ret = parser.filterExpressionEOF(); @@ -381,15 +394,9 @@ public class Parser { lexer.mode(Lexer.DEFAULT_MODE); ret = parser.expandItemsEOF(); break; - case Entity: - ret = parser.entityEOF(); - break; case Select: ret = parser.selectEOF(); break; - case Search: - ret = parser.searchInline(); - break; default: break; @@ -414,21 +421,6 @@ public class Parser { // parse switch (entryPoint) { - case All: - ret = parser.allEOF(); - break; - case Batch: - ret = parser.batchEOF(); - break; - case CrossJoin: - ret = parser.crossjoinEOF(); - break; - case Metadata: - ret = parser.metadataEOF(); - break; - case PathSegment: - ret = parser.pathSegmentEOF(); - break; case FilterExpression: lexer.mode(Lexer.DEFAULT_MODE); ret = parser.filterExpressionEOF(); @@ -441,15 +433,9 @@ public class Parser { lexer.mode(Lexer.DEFAULT_MODE); ret = parser.expandItemsEOF(); break; - case Entity: - ret = parser.entityEOF(); - break; case Select: ret = parser.selectEOF(); break; - case Search: - ret = parser.searchInline(); - break; default: break; } 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 new file mode 100644 index 000000000..985201106 --- /dev/null +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java @@ -0,0 +1,692 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.core.uri.parser; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.olingo.commons.api.edm.Edm; +import org.apache.olingo.commons.api.edm.EdmAction; +import org.apache.olingo.commons.api.edm.EdmActionImport; +import org.apache.olingo.commons.api.edm.EdmEntityContainer; +import org.apache.olingo.commons.api.edm.EdmEntitySet; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.edm.EdmFunction; +import org.apache.olingo.commons.api.edm.EdmFunctionImport; +import org.apache.olingo.commons.api.edm.EdmKeyPropertyRef; +import org.apache.olingo.commons.api.edm.EdmNavigationProperty; +import org.apache.olingo.commons.api.edm.EdmPrimitiveType; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.edm.EdmSingleton; +import org.apache.olingo.commons.api.edm.EdmStructuredType; +import org.apache.olingo.commons.api.edm.EdmType; +import org.apache.olingo.commons.api.edm.EdmTypeDefinition; +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.constants.EdmTypeKind; +import org.apache.olingo.server.api.OData; +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.core.uri.UriParameterImpl; +import org.apache.olingo.server.core.uri.UriResourceActionImpl; +import org.apache.olingo.server.core.uri.UriResourceComplexPropertyImpl; +import org.apache.olingo.server.core.uri.UriResourceCountImpl; +import org.apache.olingo.server.core.uri.UriResourceEntitySetImpl; +import org.apache.olingo.server.core.uri.UriResourceFunctionImpl; +import org.apache.olingo.server.core.uri.UriResourceNavigationPropertyImpl; +import org.apache.olingo.server.core.uri.UriResourcePrimitivePropertyImpl; +import org.apache.olingo.server.core.uri.UriResourceRefImpl; +import org.apache.olingo.server.core.uri.UriResourceSingletonImpl; +import org.apache.olingo.server.core.uri.UriResourceTypedImpl; +import org.apache.olingo.server.core.uri.UriResourceValueImpl; +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.validator.UriValidationException; + +public class ResourcePathParser { + + private final Edm edm; + private final EdmEntityContainer edmEntityContainer; + private final OData odata; + private UriTokenizer tokenizer; + + public ResourcePathParser(final Edm edm, final OData odata) { + this.edm = edm; + edmEntityContainer = edm.getEntityContainer(); + this.odata = odata; + } + + public UriResource parsePathSegment(final String pathSegment, UriResource previous) + throws UriParserException, UriValidationException { + tokenizer = new UriTokenizer(pathSegment); + + // The order is important. + // A qualified name should not be parsed as identifier and let the tokenizer halt at '.'. + + if (previous == null) { + if (tokenizer.next(TokenKind.QualifiedName)) { + throw new UriParserSemanticException("The initial segment must not be namespace-qualified.", + UriParserSemanticException.MessageKeys.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT, + new FullQualifiedName(tokenizer.getText()).getNamespace()); + } else if (tokenizer.next(TokenKind.ODataIdentifier)) { + return leadingResourcePathSegment(); + } + + } else { + if (tokenizer.next(TokenKind.REF)) { + return ref(previous); + } else if (tokenizer.next(TokenKind.VALUE)) { + return value(previous); + } else if (tokenizer.next(TokenKind.COUNT)) { + return count(previous); + } else if (tokenizer.next(TokenKind.QualifiedName)) { + return boundOperationOrTypeCast(previous); + } else if (tokenizer.next(TokenKind.ODataIdentifier)) { + return navigationOrProperty(previous); + } + } + + throw new UriParserSyntaxException("Unexpected start of resource-path segment.", + UriParserSyntaxException.MessageKeys.SYNTAX); + } + + public UriInfoImpl parseDollarEntityTypeCast(final String pathSegment) throws UriParserException { + UriInfoImpl uriInfo = new UriInfoImpl().setKind(UriInfoKind.entityId); + tokenizer = new UriTokenizer(pathSegment); + requireNext(TokenKind.QualifiedName); + final String name = tokenizer.getText(); + requireTokenEnd(); + final EdmEntityType type = edm.getEntityType(new FullQualifiedName(name)); + if (type == null) { + throw new UriParserSemanticException("Type '" + name + "' not found.", + UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, name); + } else { + uriInfo.setEntityTypeCast(type); + } + return uriInfo; + } + + public UriInfoImpl parseCrossjoinSegment(final String pathSegment) throws UriParserException { + UriInfoImpl uriInfo = new UriInfoImpl().setKind(UriInfoKind.crossjoin); + tokenizer = new UriTokenizer(pathSegment); + requireNext(TokenKind.CROSSJOIN); + requireNext(TokenKind.OPEN); + // At least one entity-set name is mandatory. Try to fetch all. + do { + requireNext(TokenKind.ODataIdentifier); + final String name = tokenizer.getText(); + final EdmEntitySet edmEntitySet = edmEntityContainer.getEntitySet(name); + if (edmEntitySet == null) { + throw new UriParserSemanticException("Expected Entity Set Name.", + UriParserSemanticException.MessageKeys.UNKNOWN_PART, name); + } else { + uriInfo.addEntitySetName(name); + } + } while (tokenizer.next(TokenKind.COMMA)); + requireNext(TokenKind.CLOSE); + requireTokenEnd(); + return uriInfo; + } + + private UriResource ref(final UriResource previous) throws UriParserException { + requireTokenEnd(); + requireTyped(previous, "$ref"); + if (((UriResourcePartTyped) previous).getType() instanceof EdmEntityType) { + return new UriResourceRefImpl(); + } else { + throw new UriParserSemanticException("$ref is only allowed on entity types.", + UriParserSemanticException.MessageKeys.ONLY_FOR_ENTITY_TYPES, "$ref"); + } + } + + private UriResource value(final UriResource previous) throws UriParserException { + requireTokenEnd(); + requireTyped(previous, "$value"); + if (!((UriResourcePartTyped) previous).isCollection()) { + return new UriResourceValueImpl(); + } else { + throw new UriParserSemanticException("$value is only allowed on typed path segments.", + UriParserSemanticException.MessageKeys.ONLY_FOR_TYPED_PARTS, "$value"); + } + } + + private UriResource count(final UriResource previous) throws UriParserException { + requireTokenEnd(); + requireTyped(previous, "$count"); + if (((UriResourcePartTyped) previous).isCollection()) { + return new UriResourceCountImpl(); + } else { + throw new UriParserSemanticException("$count is only allowed on collections.", + UriParserSemanticException.MessageKeys.ONLY_FOR_COLLECTIONS, "$count"); + } + } + + private UriResource leadingResourcePathSegment() throws UriParserException, UriValidationException { + final String oDataIdentifier = tokenizer.getText(); + + final EdmEntitySet edmEntitySet = edmEntityContainer.getEntitySet(oDataIdentifier); + if (edmEntitySet != null) { + final UriResourceEntitySetImpl entitySetResource = new UriResourceEntitySetImpl().setEntitSet(edmEntitySet); + + if (tokenizer.next(TokenKind.OPEN)) { + final List keyPredicates = keyPredicate(entitySetResource.getEntityType(), null); + entitySetResource.setKeyPredicates(keyPredicates); + } + + requireTokenEnd(); + return entitySetResource; + } + + final EdmSingleton edmSingleton = edmEntityContainer.getSingleton(oDataIdentifier); + if (edmSingleton != null) { + requireTokenEnd(); + return new UriResourceSingletonImpl().setSingleton(edmSingleton); + } + + final EdmActionImport edmActionImport = edmEntityContainer.getActionImport(oDataIdentifier); + if (edmActionImport != null) { + requireTokenEnd(); + return new UriResourceActionImpl().setActionImport(edmActionImport); + } + + final EdmFunctionImport edmFunctionImport = edmEntityContainer.getFunctionImport(oDataIdentifier); + if (edmFunctionImport != null) { + return functionCall(edmFunctionImport, null, null, false); + } + + if (tokenizer.next(TokenKind.OPEN) || tokenizer.next(TokenKind.EOF)) { + throw new UriParserSemanticException("Unexpected start of resource-path segment.", + UriParserSemanticException.MessageKeys.RESOURCE_NOT_FOUND, oDataIdentifier); + } else { + throw new UriParserSyntaxException("Unexpected start of resource-path segment.", + UriParserSyntaxException.MessageKeys.SYNTAX); + } + } + + private UriResource navigationOrProperty(final UriResource previous) + throws UriParserException, UriValidationException { + final String name = tokenizer.getText(); + + UriResourcePartTyped previousTyped = null; + EdmStructuredType structType = null; + requireTyped(previous, name); + if (((UriResourcePartTyped) previous).getType() instanceof EdmStructuredType) { + previousTyped = (UriResourcePartTyped) previous; + final EdmType previousTypeFilter = getPreviousTypeFilter(previousTyped); + structType = (EdmStructuredType) (previousTypeFilter == null ? previousTyped.getType() : previousTypeFilter); + } else { + throw new UriParserSemanticException( + "Cannot parse '" + name + "'; previous path segment is not a structural type.", + UriParserSemanticException.MessageKeys.RESOURCE_PART_MUST_BE_PRECEDED_BY_STRUCTURAL_TYPE, name); + } + + if (previousTyped.isCollection()) { + throw new UriParserSemanticException("Property '" + name + "' is not allowed after collection.", + UriParserSemanticException.MessageKeys.PROPERTY_AFTER_COLLECTION, name); + } + + final EdmProperty property = structType.getStructuralProperty(name); + if (property != null) { + return property.isPrimitive() + || property.getType().getKind() == EdmTypeKind.ENUM + || property.getType().getKind() == EdmTypeKind.DEFINITION ? + new UriResourcePrimitivePropertyImpl().setProperty(property) : + new UriResourceComplexPropertyImpl().setProperty(property); + } + final EdmNavigationProperty navigationProperty = structType.getNavigationProperty(name); + if (navigationProperty == null) { + throw new UriParserSemanticException("Property '" + name + "' not found in type '" + + structType.getFullQualifiedName().getFullQualifiedNameAsString() + "'", + UriParserSemanticException.MessageKeys.PROPERTY_NOT_IN_TYPE, + structType.getFullQualifiedName().getFullQualifiedNameAsString(), name); + } + List keyPredicate = null; + if (tokenizer.next(TokenKind.OPEN)) { + if (navigationProperty.isCollection()) { + keyPredicate = keyPredicate(navigationProperty.getType(), navigationProperty.getPartner()); + } else { + throw new UriParserSemanticException("A key is not allowed on non-collection navigation properties.", + UriParserSemanticException.MessageKeys.KEY_NOT_ALLOWED); + } + } + requireTokenEnd(); + return new UriResourceNavigationPropertyImpl() + .setNavigationProperty(navigationProperty) + .setKeyPredicates(keyPredicate); + } + + private UriResource boundOperationOrTypeCast(UriResource previous) + throws UriParserException, UriValidationException { + final FullQualifiedName name = new FullQualifiedName(tokenizer.getText()); + requireTyped(previous, name.getFullQualifiedNameAsString()); + final UriResourcePartTyped previousTyped = (UriResourcePartTyped) previous; + final EdmType previousTypeFilter = getPreviousTypeFilter(previousTyped); + final EdmType previousType = previousTypeFilter == null ? previousTyped.getType() : previousTypeFilter; + final EdmAction boundAction = edm.getBoundAction(name, + previousType.getFullQualifiedName(), + previousTyped.isCollection()); + if (boundAction != null) { + requireTokenEnd(); + return new UriResourceActionImpl().setAction(boundAction); + } + EdmStructuredType type = edm.getEntityType(name); + if (type == null) { + type = edm.getComplexType(name); + } + if (type != null) { + return typeCast(name, type, previousTyped); + } + if (tokenizer.next(TokenKind.EOF)) { + throw new UriParserSemanticException("Type '" + name.getFullQualifiedNameAsString() + "' not found.", + UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, name.getFullQualifiedNameAsString()); + } + return functionCall(null, name, + previousType.getFullQualifiedName(), + previousTyped.isCollection()); + } + + private void requireTyped(final UriResource previous, final String forWhat) throws UriParserException { + if (previous == null || !(previous instanceof UriResourcePartTyped)) { + throw new UriParserSemanticException("Path segment before '" + forWhat + "' is not typed.", + UriParserSemanticException.MessageKeys.PREVIOUS_PART_NOT_TYPED, forWhat); + } + } + + private List keyPredicate(final EdmEntityType edmEntityType, final EdmNavigationProperty partner) + throws UriParserException, UriValidationException { + final List keyPropertyRefs = edmEntityType.getKeyPropertyRefs(); + if (tokenizer.next(TokenKind.CLOSE)) { + throw new UriParserSemanticException( + "Expected " + keyPropertyRefs.size() + " key predicates but none.", + UriParserSemanticException.MessageKeys.WRONG_NUMBER_OF_KEY_PROPERTIES, + Integer.toString(keyPropertyRefs.size()), "0"); + } + List keys = new ArrayList(); + Map referencedNames = new HashMap(); + + if (partner != null) { + // Prepare list of potentially missing keys to be filled from referential constraints. + for (final String name : edmEntityType.getKeyPredicateNames()) { + final String referencedName = partner.getReferencingPropertyName(name); + if (referencedName != null) { + referencedNames.put(name, referencedName); + } + } + } + + if (tokenizer.next(TokenKind.ODataIdentifier)) { + keys.addAll(compoundKey(edmEntityType)); + } else if (keyPropertyRefs.size() - referencedNames.size() == 1) { + for (final EdmKeyPropertyRef candidate : keyPropertyRefs) { + if (referencedNames.get(candidate.getName()) == null) { + keys.add(simpleKey(candidate)); + break; + } + } + } else { + throw new UriParserSemanticException( + "Expected " + (keyPropertyRefs.size() -referencedNames.size()) + " key predicates but found one.", + UriParserSemanticException.MessageKeys.WRONG_NUMBER_OF_KEY_PROPERTIES, + Integer.toString(keyPropertyRefs.size() - referencedNames.size()), "1"); + } + + if (keys.size() < keyPropertyRefs.size() && partner != null) { + // Fill missing keys from referential constraints. + for (final String name : edmEntityType.getKeyPredicateNames()) { + boolean found = false; + for (final UriParameter key : keys) { + if (name.equals(key.getName())) { + found = true; + break; + } + } + if (!found && referencedNames.get(name) != null) { + keys.add(0, new UriParameterImpl().setName(name).setReferencedProperty(referencedNames.get(name))); + } + } + } + + // Check that all key predicates are filled from the URI. + if (keys.size() < keyPropertyRefs.size()) { + throw new UriParserSemanticException( + "Expected " + keyPropertyRefs.size() + " key predicates but found " + keys.size() + ".", + UriParserSemanticException.MessageKeys.WRONG_NUMBER_OF_KEY_PROPERTIES, + Integer.toString(keyPropertyRefs.size()), Integer.toString(keys.size())); + } else { + return keys; + } + } + + private UriParameter simpleKey(final EdmKeyPropertyRef edmKeyPropertyRef) + throws UriParserException, UriValidationException { + final EdmProperty edmProperty = edmKeyPropertyRef == null ? null : edmKeyPropertyRef.getProperty(); + if (nextPrimitiveTypeValue( + edmProperty == null ? null : (EdmPrimitiveType) edmProperty.getType(), + edmProperty == null ? false : edmProperty.isNullable())) { + final String literalValue = tokenizer.getText(); + requireNext(TokenKind.CLOSE); + return createUriParameter(edmProperty, edmKeyPropertyRef.getName(), literalValue); + } else { + throw new UriParserSemanticException("The key value is not valid.", + UriParserSemanticException.MessageKeys.INVALID_KEY_VALUE, edmKeyPropertyRef.getName()); + } + } + + private List compoundKey(final EdmEntityType edmEntityType) + throws UriParserException, UriValidationException { + + List parameters = new ArrayList(); + List parameterNames = new ArrayList(); + + // To validate that each key predicate is exactly specified once, we use a list to pick from. + List remainingKeyNames = new ArrayList(edmEntityType.getKeyPredicateNames()); + + // At least one key predicate is mandatory. Try to fetch all. + boolean hasComma = false; + do { + final String keyPredicateName = tokenizer.getText(); + if (parameterNames.contains(keyPredicateName)) { + throw new UriValidationException("Duplicated key property " + keyPredicateName, + UriValidationException.MessageKeys.DOUBLE_KEY_PROPERTY, keyPredicateName); + } + if (remainingKeyNames.isEmpty()) { + throw new UriParserSemanticException("Too many key properties.", + UriParserSemanticException.MessageKeys.WRONG_NUMBER_OF_KEY_PROPERTIES, + Integer.toString(parameters.size()), Integer.toString(parameters.size() + 1)); + } + if (!remainingKeyNames.remove(keyPredicateName)) { + throw new UriValidationException("Unknown key property " + keyPredicateName, + UriValidationException.MessageKeys.INVALID_KEY_PROPERTY, keyPredicateName); + } + parameters.add(keyValuePair(keyPredicateName, edmEntityType)); + parameterNames.add(keyPredicateName); + hasComma = tokenizer.next(TokenKind.COMMA); + if (hasComma) { + requireNext(TokenKind.ODataIdentifier); + } + } while (hasComma); + requireNext(TokenKind.CLOSE); + + return parameters; + } + + private UriParameter keyValuePair(final String keyPredicateName, final EdmEntityType edmEntityType) + throws UriParserException, UriValidationException { + final EdmKeyPropertyRef keyPropertyRef = edmEntityType.getKeyPropertyRef(keyPredicateName); + final EdmProperty edmProperty = keyPropertyRef == null ? null : keyPropertyRef.getProperty(); + if (edmProperty == null) { + throw new UriValidationException(keyPredicateName + " is not a valid key property name.", + UriValidationException.MessageKeys.INVALID_KEY_PROPERTY, keyPredicateName); + } + requireNext(TokenKind.EQ); + if (tokenizer.next(TokenKind.COMMA) || tokenizer.next(TokenKind.CLOSE) || tokenizer.next(TokenKind.EOF)) { + throw new UriParserSyntaxException("Key value expected.", UriParserSyntaxException.MessageKeys.SYNTAX); + } + if (nextPrimitiveTypeValue((EdmPrimitiveType) edmProperty.getType(), edmProperty.isNullable())) { + return createUriParameter(edmProperty, keyPredicateName, tokenizer.getText()); + } else { + throw new UriParserSemanticException(keyPredicateName + " has not a valid key value.", + UriParserSemanticException.MessageKeys.INVALID_KEY_VALUE, keyPredicateName); + } + } + + private 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 EdmPrimitiveType primitiveType = (EdmPrimitiveType) edmProperty.getType(); + try { + if (!(primitiveType.validate(primitiveType.fromUriLiteral(literalValue), 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", + UriValidationException.MessageKeys.INVALID_KEY_PROPERTY, parameterName); + } + + return new UriParameterImpl() + .setName(parameterName) + .setText("null".equals(literalValue) ? null : literalValue); + } + + private UriResource typeCast(final FullQualifiedName name, final EdmStructuredType type, + final UriResourcePartTyped previousTyped) throws UriParserException, UriValidationException { + if (type.compatibleTo(previousTyped.getType())) { + EdmType previousTypeFilter = null; + if (previousTyped instanceof UriResourceWithKeysImpl) { + if (previousTyped.isCollection()) { + previousTypeFilter = ((UriResourceWithKeysImpl) previousTyped).getTypeFilterOnCollection(); + if (previousTypeFilter != null) { + throw new UriParserSemanticException("Type filters are not chainable.", + UriParserSemanticException.MessageKeys.TYPE_FILTER_NOT_CHAINABLE, + previousTypeFilter.getName(), type.getName()); + } + ((UriResourceWithKeysImpl) previousTyped).setCollectionTypeFilter(type); + } else { + previousTypeFilter = ((UriResourceWithKeysImpl) previousTyped).getTypeFilterOnEntry(); + if (previousTypeFilter != null) { + throw new UriParserSemanticException("Type filters are not chainable.", + UriParserSemanticException.MessageKeys.TYPE_FILTER_NOT_CHAINABLE, + previousTypeFilter.getName(), type.getName()); + } + ((UriResourceWithKeysImpl) previousTyped).setEntryTypeFilter(type); + } + if (tokenizer.next(TokenKind.OPEN)) { + ((UriResourceWithKeysImpl) previousTyped).setKeyPredicates( + keyPredicate((EdmEntityType) type, null)); + } + } else { + previousTypeFilter = ((UriResourceTypedImpl) previousTyped).getTypeFilter(); + if (previousTypeFilter != null) { + throw new UriParserSemanticException("Type filters are not chainable.", + UriParserSemanticException.MessageKeys.TYPE_FILTER_NOT_CHAINABLE, + previousTypeFilter.getName(), type.getName()); + } + ((UriResourceTypedImpl) previousTyped).setTypeFilter(type); + } + requireTokenEnd(); + return null; + } else { + throw new UriParserSemanticException( + "Type filter not compatible to previous path segment: " + name.getFullQualifiedNameAsString(), + UriParserSemanticException.MessageKeys.INCOMPATIBLE_TYPE_FILTER, name.getFullQualifiedNameAsString()); + } + } + + private EdmType getPreviousTypeFilter(final UriResourcePartTyped previousTyped) { + if (previousTyped instanceof UriResourceWithKeysImpl) { + return ((UriResourceWithKeysImpl) previousTyped).getTypeFilterOnEntry() == null ? + ((UriResourceWithKeysImpl) previousTyped).getTypeFilterOnCollection() : + ((UriResourceWithKeysImpl) previousTyped).getTypeFilterOnEntry(); + } else { + return ((UriResourceTypedImpl) previousTyped).getTypeFilter(); + } + } + + private UriResource functionCall(final EdmFunctionImport edmFunctionImport, + final FullQualifiedName boundFunctionName, final FullQualifiedName bindingParameterTypeName, + final boolean isBindingParameterCollection) throws UriParserException, UriValidationException { + final List parameters = functionParameters(); + List names = new ArrayList(); + for (final UriParameter parameter : parameters) { + names.add(parameter.getName()); + } + EdmFunction function = null; + if (edmFunctionImport != null) { + function = edmFunctionImport.getUnboundFunction(names); + if (function == null) { + throw new UriParserSemanticException( + "Function of function import '" + edmFunctionImport.getName() + "' " + + "with parameters " + names.toString() + " not found.", + UriParserSemanticException.MessageKeys.FUNCTION_NOT_FOUND, edmFunctionImport.getName(), names.toString()); + } + } else { + function = edm.getBoundFunction(boundFunctionName, + bindingParameterTypeName, isBindingParameterCollection, names); + if (function == null) { + throw new UriParserSemanticException( + "Function " + boundFunctionName + " not found.", + UriParserSemanticException.MessageKeys.UNKNOWN_PART, boundFunctionName.getFullQualifiedNameAsString()); + } + } + UriResourceFunctionImpl resource = new UriResourceFunctionImpl() + .setFunctionImport(edmFunctionImport, null) + .setFunction(function) + .setParameters(parameters); + if (tokenizer.next(TokenKind.OPEN)) { + if (function.getReturnType() != null + && function.getReturnType().getType().getKind() == EdmTypeKind.ENTITY + && function.getReturnType().isCollection()) { + resource.setKeyPredicates( + keyPredicate((EdmEntityType) function.getReturnType().getType(), null)); + } else { + throw new UriParserSemanticException("A key is not allowed.", + UriParserSemanticException.MessageKeys.KEY_NOT_ALLOWED); + } + } + requireTokenEnd(); + return resource; + } + + private List functionParameters() throws UriParserException { + List parameters = new ArrayList(); + requireNext(TokenKind.OPEN); + if (tokenizer.next(TokenKind.CLOSE)) { + return parameters; + } + do { + requireNext(TokenKind.ODataIdentifier); + final String name = tokenizer.getText(); + if (parameters.contains(name)) { + throw new UriParserSemanticException("Duplicated function parameter " + name, + UriParserSemanticException.MessageKeys.INVALID_KEY_VALUE, name); + } + requireNext(TokenKind.EQ); + if (tokenizer.next(TokenKind.COMMA) || tokenizer.next(TokenKind.CLOSE) || tokenizer.next(TokenKind.EOF)) { + throw new UriParserSyntaxException("Parameter value expected.", UriParserSyntaxException.MessageKeys.SYNTAX); + } + if (nextPrimitiveValue()) { + final String literalValue = tokenizer.getText(); + UriParameterImpl parameter = new UriParameterImpl().setName(name); + parameters.add(literalValue.startsWith("@") ? + parameter.setAlias(literalValue) : + parameter.setText("null".equals(literalValue) ? null : literalValue)); + } else { + throw new UriParserSemanticException("Wrong parameter value.", + UriParserSemanticException.MessageKeys.INVALID_KEY_VALUE, ""); + } + } while (tokenizer.next(TokenKind.COMMA)); + requireNext(TokenKind.CLOSE); + return parameters; + } + + private void requireNext(final TokenKind kind) throws UriParserException { + if (!tokenizer.next(kind)) { + throw new UriParserSyntaxException("Expected token '" + kind.toString() + "' not found.", + UriParserSyntaxException.MessageKeys.SYNTAX); + } + } + + private void requireTokenEnd() throws UriParserException { + requireNext(TokenKind.EOF); + } + + private boolean nextPrimitiveTypeValue(final EdmPrimitiveType primitiveType, final boolean nullable) { + final EdmPrimitiveType type = primitiveType instanceof EdmTypeDefinition ? + ((EdmTypeDefinition) primitiveType).getUnderlyingType() : + primitiveType; + if (tokenizer.next(TokenKind.ParameterAliasName)) { + return true; + } else if (nullable && tokenizer.next(TokenKind.NULL)) { + return true; + + } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean).equals(type)) { + return tokenizer.next(TokenKind.PrimitiveBooleanValue); + } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.String).equals(type)) { + return tokenizer.next(TokenKind.PrimitiveStringValue); + } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.SByte).equals(type) + || odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Byte).equals(type) + || odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Int16).equals(type) + || odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Int32).equals(type) + || odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Int64).equals(type)) { + return tokenizer.next(TokenKind.PrimitiveIntegerValue); + } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Guid).equals(type)) { + return tokenizer.next(TokenKind.PrimitiveGuidValue); + } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Date).equals(type)) { + return tokenizer.next(TokenKind.PrimitiveDateValue); + } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.DateTimeOffset).equals(type)) { + return tokenizer.next(TokenKind.PrimitiveDateTimeOffsetValue); + } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.TimeOfDay).equals(type)) { + return tokenizer.next(TokenKind.PrimitiveTimeOfDayValue); + } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Decimal).equals(type)) { + // The order is important. + // A decimal value should not be parsed as integer and let the tokenizer stop at the decimal point. + return tokenizer.next(TokenKind.PrimitiveDecimalValue) + || tokenizer.next(TokenKind.PrimitiveIntegerValue); + } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Double).equals(type)) { + // The order is important. + // A floating-point value should not be parsed as decimal and let the tokenizer stop at 'E'. + // A decimal value should not be parsed as integer and let the tokenizer stop at the decimal point. + return tokenizer.next(TokenKind.PrimitiveDoubleValue) + || tokenizer.next(TokenKind.PrimitiveDecimalValue) + || tokenizer.next(TokenKind.PrimitiveIntegerValue); + } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Duration).equals(type)) { + return tokenizer.next(TokenKind.PrimitiveDurationValue); + } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Binary).equals(type)) { + return tokenizer.next(TokenKind.PrimitiveBinaryValue); + } else if (type.getKind() == EdmTypeKind.ENUM) { + return tokenizer.next(TokenKind.PrimitiveEnumValue); + } else { + return false; + } + } + + private boolean nextPrimitiveValue() { + return tokenizer.next(TokenKind.ParameterAliasName) + || tokenizer.next(TokenKind.NULL) + || tokenizer.next(TokenKind.PrimitiveBooleanValue) + || tokenizer.next(TokenKind.PrimitiveStringValue) + + // The order of the next seven expressions is important in order to avoid + // finding partly parsed tokens (counter-intuitive as it may be, even a GUID may start with digits ...). + || tokenizer.next(TokenKind.PrimitiveDoubleValue) + || tokenizer.next(TokenKind.PrimitiveDecimalValue) + || tokenizer.next(TokenKind.PrimitiveGuidValue) + || tokenizer.next(TokenKind.PrimitiveDateTimeOffsetValue) + || tokenizer.next(TokenKind.PrimitiveDateValue) + || tokenizer.next(TokenKind.PrimitiveTimeOfDayValue) + || tokenizer.next(TokenKind.PrimitiveIntegerValue) + + || tokenizer.next(TokenKind.PrimitiveDurationValue) + || tokenizer.next(TokenKind.PrimitiveBinaryValue) + || tokenizer.next(TokenKind.PrimitiveEnumValue); + } +} 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 1e33a190d..8740d6646 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 @@ -250,7 +250,7 @@ public class UriParseTreeVisitor extends UriParserBaseVisitor { public UriParseTreeVisitor(final Edm edm, final UriContext context) { this.edm = edm; this.context = context; - edmEntityContainer = edm.getEntityContainer(null); + edmEntityContainer = edm.getEntityContainer(); } @Override 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 new file mode 100644 index 000000000..87e09adac --- /dev/null +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java @@ -0,0 +1,592 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.core.uri.parser; + +/** + *

Simple OData URI tokenizer that works on a given string by keeping an index.

+ *

As far as feasible, it tries to work on character basis, assuming this to be faster than string operations. + * Since only the index is "moved", backing out while parsing a token is easy and used throughout. + * There is intentionally no method to push back tokens (although it would be easy to add such a method) + * because this tokenizer should behave like a classical token-consuming tokenizer.

+ */ +public class UriTokenizer { + + public enum TokenKind { + EOF, // signals the end of the string to be parsed + + // constant-value tokens (convention: uppercase) + REF, + VALUE, + COUNT, + CROSSJOIN, + OPEN, + CLOSE, + COMMA, + SEMI, + EQ, + NULL, + + // variable-value tokens (convention: mixed case) + ODataIdentifier, + QualifiedName, + ParameterAliasName, + + PrimitiveBooleanValue, + PrimitiveStringValue, + PrimitiveIntegerValue, + PrimitiveGuidValue, + PrimitiveDateValue, + PrimitiveDateTimeOffsetValue, + PrimitiveTimeOfDayValue, + PrimitiveDecimalValue, + PrimitiveDoubleValue, + PrimitiveDurationValue, + PrimitiveBinaryValue, + PrimitiveEnumValue, + + jsonArrayOrObject + } + + private final String pathSegment; + + private int startIndex = 0; + private int index = 0; + + public UriTokenizer(final String pathSegment) { + this.pathSegment = pathSegment == null ? "" : pathSegment; + } + + /** Returns the string value corresponding to the last successful {@link #next(TokenKind)} call. */ + public String getText() { + return pathSegment.substring(startIndex, index); + } + + /** + * Tries to find a token of the given token kind at the current index. + * The order in which this method is called with different token kinds is important, + * not only for performance reasons but also if tokens can start with the same characters + * (e.g., a qualified name starts with an OData identifier). + * @param allowedTokenKind the kind of token to expect + * @return true if the token is found; false otherwise + * @see #getText() + */ + public boolean next(final TokenKind allowedTokenKind) { + if (allowedTokenKind == null) { + return false; + } + + boolean found = false; + final int previousIndex = index; + switch (allowedTokenKind) { + // Constants + case REF: + found = nextConstant("$ref"); + break; + case VALUE: + found = nextConstant("$value"); + break; + case COUNT: + found = nextConstant("$count"); + break; + case CROSSJOIN: + found = nextConstant("$crossjoin"); + break; + case OPEN: + found = nextCharacter('('); + break; + case CLOSE: + found = nextCharacter(')'); + break; + case COMMA: + found = nextCharacter(','); + break; + case SEMI: + found = nextCharacter(';'); + break; + case EQ: + found = nextCharacter('='); + break; + case NULL: + found = nextConstant("null"); + break; + case EOF: + found = index >= pathSegment.length(); + break; + + // Identifiers + case ODataIdentifier: + found = nextODataIdentifier(); + break; + case QualifiedName: + found = nextQualifiedName(); + break; + case ParameterAliasName: + found = nextParameterAliasName(); + break; + + // Primitive Values + case PrimitiveBooleanValue: + found = nextBooleanValue(); + break; + case PrimitiveStringValue: + found = nextStringValue(); + break; + case PrimitiveIntegerValue: + found = nextIntegerValue(true); + break; + case PrimitiveGuidValue: + found = nextGuidValue(); + break; + case PrimitiveDateValue: + found = nextDateValue(); + break; + case PrimitiveDateTimeOffsetValue: + found = nextDateTimeOffsetValue(); + break; + case PrimitiveTimeOfDayValue: + found = nextTimeOfDayValue(); + break; + case PrimitiveDecimalValue: + found = nextDecimalValue(); + break; + case PrimitiveDoubleValue: + found = nextDoubleValue(); + break; + case PrimitiveDurationValue: + found = nextDurationValue(); + break; + case PrimitiveBinaryValue: + found = nextBinaryValue(); + break; + case PrimitiveEnumValue: + found = nextEnumValue(); + break; + + // Primitive Values + case jsonArrayOrObject: + found = nextJsonArrayOrObject(); + break; + } + + if (found) { + startIndex = previousIndex; + } else { + index = previousIndex; + } + return found; + } + + private boolean nextConstant(final String constant) { + if (pathSegment.startsWith(constant, index)) { + index += constant.length(); + return true; + } else { + return false; + } + } + + private boolean nextConstantIgnoreCase(final String constant) { + final int length = constant.length(); + if (index + length <= pathSegment.length() + && constant.equalsIgnoreCase(pathSegment.substring(index, index + length))) { + index += length; + return true; + } else { + return false; + } + } + + /** + * Moves past the given character if found; otherwise leaves the index unchanged. + * @return whether the given character has been found at the current index + */ + private boolean nextCharacter(final char character) { + if (index < pathSegment.length() && pathSegment.charAt(index) == character) { + index++; + return true; + } else { + return false; + } + } + + /** + * Moves past the next character if it is in the given character range; + * otherwise leaves the index unchanged. + * @return whether the given character has been found at the current index + */ + private boolean nextCharacterRange(final char from, final char to) { + if (index < pathSegment.length()) { + final char code = pathSegment.charAt(index); + if (code >= from && code <= to) { + index++; + return true; + } + } + return false; + } + + /** + * Moves past a digit character ('0' to '9') if found; otherwise leaves the index unchanged. + * @return whether a digit character has been found at the current index + */ + private boolean nextDigit() { + return nextCharacterRange('0', '9'); + } + + /** + * Moves past a hexadecimal digit character ('0' to '9', 'A' to 'F', or 'a' to 'f') if found; + * otherwise leaves the index unchanged. + * @return whether a hexadecimal digit character has been found at the current index + */ + private boolean nextHexDigit() { + return nextCharacterRange('0', '9') || nextCharacterRange('A', 'F') || nextCharacterRange('a', 'f'); + } + + /** + * Moves past a base64 character ('0' to '9', 'A' to 'Z', 'a' to 'z', '-', or '_') if found; + * otherwise leaves the index unchanged. + * @return whether a base64 character has been found at the current index + */ + private boolean nextBase64() { + return nextCharacterRange('0', '9') || nextCharacterRange('A', 'Z') || nextCharacterRange('a', 'z') + || nextCharacter('-') || nextCharacter('_'); + } + + /** + * Moves past a sign character ('+' or '-') if found; otherwise leaves the index unchanged. + * @return whether a sign character has been found at the current index + */ + private boolean nextSign() { + return nextCharacter('+') || nextCharacter('-'); + } + + private boolean nextODataIdentifier() { + int count = 0; + if (index < pathSegment.length()) { + int code = pathSegment.codePointAt(index); + if (Character.isUnicodeIdentifierStart(code) || code == '_') { + count++; + // Unicode characters outside of the Basic Multilingual Plane are represented as two Java characters. + index += Character.isSupplementaryCodePoint(code) ? 2 : 1; + while (index < pathSegment.length() && count < 128) { + code = pathSegment.codePointAt(index); + if (Character.isUnicodeIdentifierPart(code) && !Character.isISOControl(code)) { + count++; + // Unicode characters outside of the Basic Multilingual Plane are represented as two Java characters. + index += Character.isSupplementaryCodePoint(code) ? 2 : 1; + } else { + break; + } + } + } + } + return count > 0; + } + + private boolean nextQualifiedName() { + int count = 0; + do { + if (nextODataIdentifier()) { + count++; + } else { + return false; + } + } while (nextCharacter('.')); + return count >= 2; + } + + private boolean nextParameterAliasName() { + return nextCharacter('@') && nextODataIdentifier(); + } + + private boolean nextBooleanValue() { + return nextConstantIgnoreCase("true") || nextConstantIgnoreCase("false"); + } + + private boolean nextStringValue() { + if (!nextCharacter('\'')) { + return false; + } + while (index < pathSegment.length()) { + if (pathSegment.charAt(index) == '\'') { + // If a single quote is followed by another single quote, + // it represents one single quote within the string literal, + // otherwise it marks the end of the string literal. + if (index + 1 < pathSegment.length() && pathSegment.charAt(index + 1) == '\'') { + index++; + } else { + break; + } + } + index++; + } + return nextCharacter('\''); + } + + private boolean nextIntegerValue(final boolean signed) { + if (signed) { + nextSign(); + } + boolean hasDigits = false; + while (nextDigit()) { + hasDigits = true; + } + return hasDigits; + } + + /** Finds and returns only decimal-number tokens with a fractional part. + * Whole numbers must be found with {@link #nextIntegerValue()}. + */ + private boolean nextDecimalValue() { + return nextIntegerValue(true) && nextCharacter('.') && nextIntegerValue(false); + } + + /** + * Finds and returns only floating-point-number tokens with an exponential part + * and the special three constants "NaN", "-INF", and "INF". + * Whole numbers must be found with {@link #nextIntegerValue()}. + * Decimal numbers must be found with {@link #nextDecimalValue()}. + */ + private boolean nextDoubleValue() { + if (nextConstant("NaN") || nextConstant("-INF") || nextConstant("INF")) { + return true; + } else { + if (!nextIntegerValue(true)) { + return false; + } + if (nextCharacter('.') && !nextIntegerValue(false)) { + return false; + } + return (nextCharacter('E') || nextCharacter('e')) && nextIntegerValue(true); + } + } + + private boolean nextGuidValue() { + return nextHexDigit() && nextHexDigit() && nextHexDigit() && nextHexDigit() + && nextHexDigit() && nextHexDigit() && nextHexDigit() && nextHexDigit() + && nextCharacter('-') + && nextHexDigit() && nextHexDigit() && nextHexDigit() && nextHexDigit() + && nextCharacter('-') + && nextHexDigit() && nextHexDigit() && nextHexDigit() && nextHexDigit() + && nextCharacter('-') + && nextHexDigit() && nextHexDigit() && nextHexDigit() && nextHexDigit() + && nextCharacter('-') + && nextHexDigit() && nextHexDigit() && nextHexDigit() && nextHexDigit() + && nextHexDigit() && nextHexDigit() && nextHexDigit() && nextHexDigit() + && nextHexDigit() && nextHexDigit() && nextHexDigit() && nextHexDigit(); + } + + private boolean nextYear() { + nextCharacter('-'); + if (nextCharacter('0')) { + return nextDigit() && nextDigit() && nextDigit(); + } else if (nextCharacterRange('1', '9')) { + int count = 0; + while (nextDigit()) { + count++; + } + return count >= 3; + } else { + return false; + } + } + + private boolean nextDateValue() { + return nextYear() + && nextCharacter('-') + && (nextCharacter('0') && nextCharacterRange('1', '9') + || nextCharacter('1') && nextCharacterRange('0', '2')) + && nextCharacter('-') + && (nextCharacter('0') && nextCharacterRange('1', '9') + || nextCharacterRange('1', '2') && nextDigit() + || nextCharacter('3') && nextCharacterRange('0', '1')); + } + + private boolean nextHours() { + return nextCharacterRange('0', '1') && nextDigit() + || nextCharacter('2') && nextCharacterRange('0', '3'); + } + + private boolean nextMinutesOrSeconds() { + return nextCharacterRange('0', '5') && nextDigit(); + } + + private boolean nextDateTimeOffsetValue() { + return nextDateValue() + && (nextCharacter('T') || nextCharacter('t')) + && nextTimeOfDayValue() + && (nextCharacter('Z') + || nextCharacter('z') + || nextSign() && nextHours() && nextCharacter(':') && nextMinutesOrSeconds()); + } + + private boolean nextTimeOfDayValue() { + if (nextHours() && nextCharacter(':') && nextMinutesOrSeconds()) { + if (nextCharacter(':')) { + if (nextMinutesOrSeconds()) { + if (nextCharacter('.') && !nextIntegerValue(false)) { + return false; + } + } else { + return false; + } + } + return true; + } else { + return false; + } + } + + private boolean nextDurationValue() { + if (nextConstantIgnoreCase("duration") && nextCharacter('\'')) { + nextSign(); + if (nextCharacter('P') || nextCharacter('p')) { + if (nextIntegerValue(false)) { + if (!(nextCharacter('D') || nextCharacter('d'))) { + return false; + } + } + if (nextCharacter('T') || nextCharacter('t')) { + boolean hasNumber = false; + if (nextIntegerValue(false)) { + hasNumber = true; + if (nextCharacter('H') || nextCharacter('h')) { + hasNumber = false; + } + } + if (hasNumber || nextIntegerValue(false)) { + hasNumber = true; + if (nextCharacter('M') || nextCharacter('m')) { + hasNumber = false; + } + } + if (hasNumber || nextIntegerValue(false)) { + if (nextCharacter('.')) { + if (!nextIntegerValue(false)) { + return false; + } + } + if (!(nextCharacter('S') || nextCharacter('s'))) { + return false; + } + } + } + return nextCharacter('\''); + } + } + return false; + } + + private boolean nextBinaryValue() { + if (nextConstantIgnoreCase("binary") && nextCharacter('\'')) { + int lastGoodIndex = index; + while (nextBase64() && nextBase64() && nextBase64() && nextBase64()) { + lastGoodIndex += 4; + } + index = lastGoodIndex; + if (nextBase64() && nextBase64() + && (nextCharacter('A') || nextCharacter('E') || nextCharacter('I') || nextCharacter('M') + || nextCharacter('Q') || nextCharacter('U') || nextCharacter('Y') || nextCharacter('c') + || nextCharacter('g') || nextCharacter('k') || nextCharacter('o') || nextCharacter('s') + || nextCharacter('w') || nextCharacter('0') || nextCharacter('4') || nextCharacter('8'))) { + nextCharacter('='); + } else { + index = lastGoodIndex; + if (nextBase64()) { + if (nextCharacter('A') || nextCharacter('Q') || nextCharacter('g') || nextCharacter('w')) { + nextConstant("=="); + } else { + return false; + } + } + } + return nextCharacter('\''); + } + return false; + } + + private boolean nextEnumValue() { + if (nextQualifiedName() && nextCharacter('\'')) { + do { + if (!(nextODataIdentifier() || nextIntegerValue(true))) { + return false; + } + } while (nextCharacter(',')); + return nextCharacter('\''); + } + return false; + } + + private boolean nextJsonString() { + if (nextCharacter('"')) { + do { + if (nextCharacter('\\')) { + if (!(nextCharacter('b') || nextCharacter('t') + || nextCharacter('n') || nextCharacter('f') || nextCharacter('r') + || nextCharacter('"') || nextCharacter('/') || nextCharacter('\\') + || nextCharacter('u') && nextHexDigit() && nextHexDigit() && nextHexDigit() && nextHexDigit())) { + return false; + } + } else if (nextCharacter('"')) { + return true; + } else { + index++; + } + } while (index < pathSegment.length()); + return false; + } + return false; + } + + private boolean nextJsonValue() { + return nextConstant("null") || nextConstant("true") || nextConstant("false") + // If a double or decimal number is not found, the index must be reset; the internal methods don't do that. + || next(TokenKind.PrimitiveDoubleValue) || next(TokenKind.PrimitiveDecimalValue) || nextIntegerValue(true) + || nextJsonString() + || nextJsonArrayOrObject(); + } + + private boolean nextJsonMember() { + return nextJsonString() && nextCharacter(':') && nextJsonValue(); + } + + private boolean nextJsonArrayOrObject() { + if (nextCharacter('[')) { + if (nextJsonValue()) { + while (nextCharacter(',')) { + if (!nextJsonValue()) { + return false; + } + } + } + return nextCharacter(']'); + } else if (nextCharacter('{')) { + if (nextJsonMember()) { + while (nextCharacter(',')) { + if (!nextJsonMember()) { + return false; + } + } + } + return nextCharacter('}'); + } else { + return false; + } + } +} 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 f840e8e5a..3ccd97a81 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 @@ -24,7 +24,6 @@ import java.util.Map; import org.apache.olingo.commons.api.edm.EdmEntityType; import org.apache.olingo.commons.api.edm.EdmFunction; -import org.apache.olingo.commons.api.edm.EdmFunctionImport; import org.apache.olingo.commons.api.edm.EdmKeyPropertyRef; import org.apache.olingo.commons.api.edm.EdmPrimitiveType; import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; @@ -223,14 +222,7 @@ public class UriValidator { private RowIndexForUriType rowIndexForResourceKind(final UriInfo uriInfo) throws UriValidationException { RowIndexForUriType idx; - - final int nonComposableFunctionIndex = getIndexOfLastNonComposableFunction(uriInfo); - if(nonComposableFunctionIndex != -1 && (uriInfo.getUriResourceParts().size() - 1) > nonComposableFunctionIndex) { - throw new UriValidationException("Non composable functions followed by further resource parts are not allowed", - UriValidationException.MessageKeys.UNALLOWED_RESOURCE_PATH, - uriInfo.getUriResourceParts().get(nonComposableFunctionIndex + 1).getSegmentValue()); - } - + int lastPathSegmentIndex = uriInfo.getUriResourceParts().size() - 1; UriResource lastPathSegment = uriInfo.getUriResourceParts().get(lastPathSegmentIndex); @@ -249,11 +241,7 @@ public class UriValidator { idx = rowIndexForEntitySet(lastPathSegment); break; case function: - if(nonComposableFunctionIndex == -1) { - idx = rowIndexForFunction(lastPathSegment); - } else { - idx = RowIndexForUriType.none; - } + idx = rowIndexForFunction(lastPathSegment); break; case primitiveProperty: idx = rowIndexForPrimitiveProperty(lastPathSegment); @@ -278,21 +266,6 @@ public class UriValidator { return idx; } - private int getIndexOfLastNonComposableFunction(final UriInfo uriInfo) { - for(int i = 0; i < uriInfo.getUriResourceParts().size(); i++) { - final UriResource resourcePath = uriInfo.getUriResourceParts().get(i); - - if(resourcePath instanceof UriResourceFunction) { - final UriResourceFunction resourceFuntion = (UriResourceFunction) resourcePath; - if(!resourceFuntion.getFunction().isComposable()) { - return i; - } - } - } - - return -1; - } - private RowIndexForUriType rowIndexForValue(final UriInfo uriInfo) throws UriValidationException { RowIndexForUriType idx; int secondLastPathSegmentIndex = uriInfo.getUriResourceParts().size() - 2; @@ -309,9 +282,7 @@ public class UriValidator { break; case function: UriResourceFunction uriFunction = (UriResourceFunction) secondLastPathSegment; - final EdmFunctionImport functionImport = uriFunction.getFunctionImport(); - final EdmFunction function = functionImport == null ? - uriFunction.getFunction() : functionImport.getUnboundFunctions().get(0); + final EdmFunction function = uriFunction.getFunction(); idx = function.getReturnType().getType().getKind() == EdmTypeKind.ENTITY ? RowIndexForUriType.mediaStream : RowIndexForUriType.propertyPrimitiveValue; break; @@ -351,32 +322,33 @@ public class UriValidator { } private RowIndexForUriType rowIndexForFunction(final UriResource lastPathSegment) throws UriValidationException { - RowIndexForUriType idx; - UriResourceFunction urf = (UriResourceFunction) lastPathSegment; - EdmReturnType rt = urf.getFunction().getReturnType(); + final UriResourceFunction uriFunction = (UriResourceFunction) lastPathSegment; + final EdmReturnType returnType = uriFunction.getFunction().getReturnType(); - if(!urf.getFunction().isComposable()) { + if (!uriFunction.getFunction().isComposable()) { return RowIndexForUriType.none; } - - - switch (rt.getType().getKind()) { + + RowIndexForUriType idx; + switch (returnType.getType().getKind()) { case ENTITY: - idx = rt.isCollection() && urf.getKeyPredicates().isEmpty() ? + idx = returnType.isCollection() && uriFunction.getKeyPredicates().isEmpty() ? RowIndexForUriType.entitySet : RowIndexForUriType.entity; break; case PRIMITIVE: case ENUM: case DEFINITION: - idx = rt.isCollection() ? RowIndexForUriType.propertyPrimitiveCollection : RowIndexForUriType.propertyPrimitive; + idx = returnType.isCollection() ? RowIndexForUriType.propertyPrimitiveCollection : + RowIndexForUriType.propertyPrimitive; break; case COMPLEX: - idx = rt.isCollection() ? RowIndexForUriType.propertyComplexCollection : RowIndexForUriType.propertyComplex; + idx = returnType.isCollection() ? RowIndexForUriType.propertyComplexCollection : + RowIndexForUriType.propertyComplex; break; default: - throw new UriValidationException("Unsupported function return type: " + rt.getType().getKind(), + throw new UriValidationException("Unsupported function return type: " + returnType.getType().getKind(), UriValidationException.MessageKeys.UNSUPPORTED_FUNCTION_RETURN_TYPE, - rt.getType().getKind().toString()); + returnType.getType().getKind().toString()); } return idx; @@ -447,9 +419,7 @@ public class UriValidator { break; case function: final UriResourceFunction uriFunction = (UriResourceFunction) secondLastPathSegment; - final EdmFunctionImport functionImport = uriFunction.getFunctionImport(); - final EdmFunction function = functionImport == null ? - uriFunction.getFunction() : functionImport.getUnboundFunctions().get(0); + final EdmFunction function = uriFunction.getFunction(); final EdmType returnType = function.getReturnType().getType(); switch (returnType.getKind()) { case ENTITY: @@ -560,8 +530,8 @@ public class UriValidator { private void validateParameters(final UriInfo uriInfo) throws UriValidationException { for (UriResource pathSegment : uriInfo.getUriResourceParts()) { final boolean isFunction = pathSegment.getKind() == UriResourceKind.function; - - if(isFunction) { + + if (isFunction) { final UriResourceFunction functionPathSegement = (UriResourceFunction) pathSegment; final EdmFunction edmFuntion = functionPathSegement.getFunction(); @@ -613,7 +583,7 @@ public class UriValidator { for (UriResource pathSegment : uriInfo.getUriResourceParts()) { final boolean isEntitySet = pathSegment.getKind() == UriResourceKind.entitySet; final boolean isEntityColFunction = isEntityColFunction(pathSegment); - + if (isEntitySet || pathSegment.getKind() == UriResourceKind.navigationProperty || isEntityColFunction) { final List keyPredicates = isEntitySet ? ((UriResourceEntitySet) pathSegment).getKeyPredicates() : @@ -680,7 +650,7 @@ public class UriValidator { } private boolean isEntityColFunction(final UriResource pathSegment) { - if(pathSegment.getKind() == UriResourceKind.function) { + if (pathSegment.getKind() == UriResourceKind.function) { final UriResourceFunction resourceFunction = (UriResourceFunction) pathSegment; final EdmReturnType returnType = resourceFunction.getFunction().getReturnType(); @@ -689,7 +659,7 @@ public class UriValidator { return false; } } - + private void validatePropertyOperations(final UriInfo uriInfo, final HttpMethod method) throws UriValidationException { final List parts = uriInfo.getUriResourceParts(); diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java new file mode 100644 index 000000000..a9e97ce89 --- /dev/null +++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java @@ -0,0 +1,369 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.olingo.server.core.uri.parser; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind; +import org.junit.Test; + +public class UriTokenizerTest { + + @Test + public void nullOK() { + assertFalse(new UriTokenizer(null).next(null)); + assertTrue(new UriTokenizer(null).next(TokenKind.EOF)); + } + + @Test + public void constants() { + final UriTokenizer tokenizer = new UriTokenizer("$ref"); + assertTrue(tokenizer.next(TokenKind.REF)); + assertEquals("$ref", tokenizer.getText()); + assertTrue(tokenizer.next(TokenKind.EOF)); + assertTrue(tokenizer.next(TokenKind.EOF)); + + assertTrue(new UriTokenizer("$value").next(TokenKind.VALUE)); + assertTrue(new UriTokenizer("$count").next(TokenKind.COUNT)); + assertTrue(new UriTokenizer("$crossjoin").next(TokenKind.CROSSJOIN)); + assertTrue(new UriTokenizer("null").next(TokenKind.NULL)); + + wrongToken(TokenKind.REF, "$ref", 'x'); + } + + @Test + public void sequence() { + final UriTokenizer tokenizer = new UriTokenizer("(A=1,B=2);"); + assertTrue(tokenizer.next(TokenKind.OPEN)); + assertFalse(tokenizer.next(TokenKind.OPEN)); + assertTrue(tokenizer.next(TokenKind.ODataIdentifier)); + assertEquals("A", tokenizer.getText()); + assertTrue(tokenizer.next(TokenKind.EQ)); + assertTrue(tokenizer.next(TokenKind.PrimitiveIntegerValue)); + assertEquals("1", tokenizer.getText()); + assertTrue(tokenizer.next(TokenKind.COMMA)); + assertTrue(tokenizer.next(TokenKind.ODataIdentifier)); + assertEquals("B", tokenizer.getText()); + assertTrue(tokenizer.next(TokenKind.EQ)); + assertTrue(tokenizer.next(TokenKind.PrimitiveIntegerValue)); + assertEquals("2", tokenizer.getText()); + assertFalse(tokenizer.next(TokenKind.EOF)); + assertTrue(tokenizer.next(TokenKind.CLOSE)); + assertTrue(tokenizer.next(TokenKind.SEMI)); + assertTrue(tokenizer.next(TokenKind.EOF)); + } + + @Test + public void identifier() { + assertTrue(new UriTokenizer("name").next(TokenKind.ODataIdentifier)); + assertTrue(new UriTokenizer("_name").next(TokenKind.ODataIdentifier)); + assertFalse(new UriTokenizer("1name").next(TokenKind.ODataIdentifier)); + assertFalse(new UriTokenizer("").next(TokenKind.ODataIdentifier)); + + final String outsideBmpLetter = String.valueOf(Character.toChars(0x10330)); + UriTokenizer tokenizer = new UriTokenizer( + outsideBmpLetter + "name1\u0300a\u0600b\uFE4F" + outsideBmpLetter + "end\b"); + assertTrue(tokenizer.next(TokenKind.ODataIdentifier)); + assertEquals(outsideBmpLetter + "name1\u0300a\u0600b\uFE4F" + outsideBmpLetter + "end", tokenizer.getText()); + + // Identifiers consist of up to 128 characters. Check that the identifier does not have more characters. + final String name = "Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch"; // Do you know this village? + tokenizer = new UriTokenizer(name + '_' + name + "_0123456789X"); + assertTrue(tokenizer.next(TokenKind.ODataIdentifier)); + assertEquals(name + '_' + name + "_0123456789", tokenizer.getText()); + tokenizer.next(TokenKind.ODataIdentifier); + assertEquals("X", tokenizer.getText()); + + wrongToken(TokenKind.ODataIdentifier, "_", '.'); + wrongToken(TokenKind.ODataIdentifier, "_", ','); + } + + @Test + public void qualifiedName() { + assertTrue(new UriTokenizer("namespace.name").next(TokenKind.QualifiedName)); + + final UriTokenizer tokenizer = new UriTokenizer("multi.part.namespace.name"); + assertTrue(tokenizer.next(TokenKind.QualifiedName)); + assertTrue(tokenizer.next(TokenKind.EOF)); + + assertFalse(new UriTokenizer("name").next(TokenKind.QualifiedName)); + assertFalse(new UriTokenizer("namespace..name").next(TokenKind.QualifiedName)); + assertFalse(new UriTokenizer("").next(TokenKind.QualifiedName)); + wrongToken(TokenKind.QualifiedName, "namespace._", ','); + } + + @Test + public void alias() { + assertTrue(new UriTokenizer("@name").next(TokenKind.ParameterAliasName)); + assertTrue(new UriTokenizer("@_name").next(TokenKind.ParameterAliasName)); + assertFalse(new UriTokenizer("name").next(TokenKind.ParameterAliasName)); + assertFalse(new UriTokenizer("@").next(TokenKind.ParameterAliasName)); + assertFalse(new UriTokenizer("@1").next(TokenKind.ParameterAliasName)); + } + + @Test + public void booleanValue() { + assertTrue(new UriTokenizer("true").next(TokenKind.PrimitiveBooleanValue)); + assertTrue(new UriTokenizer("tRuE").next(TokenKind.PrimitiveBooleanValue)); + assertTrue(new UriTokenizer("false").next(TokenKind.PrimitiveBooleanValue)); + assertTrue(new UriTokenizer("False").next(TokenKind.PrimitiveBooleanValue)); + + wrongToken(TokenKind.PrimitiveBooleanValue, "true", 'x'); + } + + @Test + public void string() { + assertTrue(new UriTokenizer("'ABC'").next(TokenKind.PrimitiveStringValue)); + assertTrue(new UriTokenizer("'€\uFDFC'").next(TokenKind.PrimitiveStringValue)); + assertTrue(new UriTokenizer('\'' + String.valueOf(Character.toChars(0x1F603)) + '\'') + .next(TokenKind.PrimitiveStringValue)); + + final UriTokenizer tokenizer = new UriTokenizer("'AB''''C'''D"); + assertTrue(tokenizer.next(TokenKind.PrimitiveStringValue)); + assertEquals("'AB''''C'''", tokenizer.getText()); + assertTrue(tokenizer.next(TokenKind.ODataIdentifier)); + assertEquals("D", tokenizer.getText()); + + assertFalse(new UriTokenizer("A").next(TokenKind.PrimitiveStringValue)); + assertFalse(new UriTokenizer("'A").next(TokenKind.PrimitiveStringValue)); + } + + @Test + public void integer() { + assertTrue(new UriTokenizer("1").next(TokenKind.PrimitiveIntegerValue)); + assertTrue(new UriTokenizer("1.").next(TokenKind.PrimitiveIntegerValue)); + assertFalse(new UriTokenizer(".1").next(TokenKind.PrimitiveIntegerValue)); + assertTrue(new UriTokenizer("-1").next(TokenKind.PrimitiveIntegerValue)); + assertTrue(new UriTokenizer("1234567890").next(TokenKind.PrimitiveIntegerValue)); + } + + @Test + public void guid() { + assertTrue(new UriTokenizer("12345678-abcd-ef12-1234-567890ABCDEF").next(TokenKind.PrimitiveGuidValue)); + wrongToken(TokenKind.PrimitiveGuidValue, "12345678-1234-1234-1234-123456789ABC", 'G'); + } + + @Test + public void date() { + assertTrue(new UriTokenizer("12345-12-25").next(TokenKind.PrimitiveDateValue)); + assertTrue(new UriTokenizer("-0001-12-24").next(TokenKind.PrimitiveDateValue)); + assertFalse(new UriTokenizer("1234-13-01").next(TokenKind.PrimitiveDateValue)); + assertFalse(new UriTokenizer("1234-12-32").next(TokenKind.PrimitiveDateValue)); + assertFalse(new UriTokenizer("123-01-01").next(TokenKind.PrimitiveDateValue)); + assertFalse(new UriTokenizer("1234-00-01").next(TokenKind.PrimitiveDateValue)); + assertFalse(new UriTokenizer("1234-01-00").next(TokenKind.PrimitiveDateValue)); + wrongToken(TokenKind.PrimitiveDateValue, "2000-12-29", 'A'); + wrongToken(TokenKind.PrimitiveDateValue, "0001-01-01", 'A'); + wrongToken(TokenKind.PrimitiveDateValue, "-12345-01-31", 'A'); + } + + @Test + public void dateTimeOffset() { + assertTrue(new UriTokenizer("1234-12-25T11:12:13.456Z").next(TokenKind.PrimitiveDateTimeOffsetValue)); + assertTrue(new UriTokenizer("-1234-12-25t01:12z").next(TokenKind.PrimitiveDateTimeOffsetValue)); + assertTrue(new UriTokenizer("-1234-12-25T21:22:23+01:00").next(TokenKind.PrimitiveDateTimeOffsetValue)); + assertTrue(new UriTokenizer("1234-12-25T11:12:13-00:30").next(TokenKind.PrimitiveDateTimeOffsetValue)); + assertFalse(new UriTokenizer("1234-10-01").next(TokenKind.PrimitiveDateTimeOffsetValue)); + wrongToken(TokenKind.PrimitiveDateTimeOffsetValue, "-1234-12-25T11:12:13.456+01:00", 'P'); + } + + @Test + public void timeOfDay() { + assertTrue(new UriTokenizer("11:12:13").next(TokenKind.PrimitiveTimeOfDayValue)); + assertTrue(new UriTokenizer("11:12:13.456").next(TokenKind.PrimitiveTimeOfDayValue)); + assertFalse(new UriTokenizer("24:00:00").next(TokenKind.PrimitiveTimeOfDayValue)); + assertFalse(new UriTokenizer("01:60:00").next(TokenKind.PrimitiveTimeOfDayValue)); + assertFalse(new UriTokenizer("01:00:60").next(TokenKind.PrimitiveTimeOfDayValue)); + assertFalse(new UriTokenizer("01:00:00.").next(TokenKind.PrimitiveTimeOfDayValue)); + assertFalse(new UriTokenizer("0:02:03").next(TokenKind.PrimitiveTimeOfDayValue)); + assertFalse(new UriTokenizer("01:0:03").next(TokenKind.PrimitiveTimeOfDayValue)); + assertFalse(new UriTokenizer("01:02:0").next(TokenKind.PrimitiveTimeOfDayValue)); + wrongToken(TokenKind.PrimitiveTimeOfDayValue, "11:12", '-'); + } + + @Test + public void decimal() { + assertTrue(new UriTokenizer("1.2").next(TokenKind.PrimitiveDecimalValue)); + assertFalse(new UriTokenizer(".1").next(TokenKind.PrimitiveDecimalValue)); + assertTrue(new UriTokenizer("-12.34").next(TokenKind.PrimitiveDecimalValue)); + assertTrue(new UriTokenizer("1234567890.0123456789").next(TokenKind.PrimitiveDecimalValue)); + assertFalse(new UriTokenizer("0,1").next(TokenKind.PrimitiveDecimalValue)); + assertFalse(new UriTokenizer("0..1").next(TokenKind.PrimitiveDecimalValue)); + } + + @Test + public void doubleValue() { + assertTrue(new UriTokenizer("NaN").next(TokenKind.PrimitiveDoubleValue)); + assertTrue(new UriTokenizer("-INF").next(TokenKind.PrimitiveDoubleValue)); + assertTrue(new UriTokenizer("INF").next(TokenKind.PrimitiveDoubleValue)); + assertFalse(new UriTokenizer("inf").next(TokenKind.PrimitiveDoubleValue)); + assertTrue(new UriTokenizer("1.2E3").next(TokenKind.PrimitiveDoubleValue)); + assertTrue(new UriTokenizer("-12.34e-05").next(TokenKind.PrimitiveDoubleValue)); + assertTrue(new UriTokenizer("1E2").next(TokenKind.PrimitiveDoubleValue)); + assertFalse(new UriTokenizer("1.E2").next(TokenKind.PrimitiveDoubleValue)); + wrongToken(TokenKind.PrimitiveDoubleValue, "-12.34E+5", 'i'); + } + + @Test + public void duration() { + assertTrue(new UriTokenizer("duration'P'").next(TokenKind.PrimitiveDurationValue)); + assertTrue(new UriTokenizer("DURATION'P1D'").next(TokenKind.PrimitiveDurationValue)); + assertTrue(new UriTokenizer("duration'PT'").next(TokenKind.PrimitiveDurationValue)); + assertTrue(new UriTokenizer("duration'PT1H'").next(TokenKind.PrimitiveDurationValue)); + assertTrue(new UriTokenizer("duration'pt1M'").next(TokenKind.PrimitiveDurationValue)); + assertTrue(new UriTokenizer("duration'PT1S'").next(TokenKind.PrimitiveDurationValue)); + assertTrue(new UriTokenizer("duration'PT1.2s'").next(TokenKind.PrimitiveDurationValue)); + assertTrue(new UriTokenizer("duration'-p1dt2h3m4.5s'").next(TokenKind.PrimitiveDurationValue)); + assertFalse(new UriTokenizer("-p1dt2h3m4.5s").next(TokenKind.PrimitiveDurationValue)); + assertFalse(new UriTokenizer("duration'-p1dt2h3m4.5s").next(TokenKind.PrimitiveDurationValue)); + assertFalse(new UriTokenizer("duration'2h3m4s'").next(TokenKind.PrimitiveDurationValue)); + wrongToken(TokenKind.PrimitiveDurationValue, "duration'P1DT2H3M4.5S'", ':'); + } + + @Test + public void binary() { + assertTrue(new UriTokenizer("binary''").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("Binary'bm93'").next(TokenKind.PrimitiveBinaryValue)); + + // all cases with three base64 characters (and one fill character) at the end + assertTrue(new UriTokenizer("binary'QUA='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'QUE='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'QUI='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'QUM='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'QUQ='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'QUU='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'QUY='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'QUc='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'QUg='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'QUk='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'QUo='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'QUs='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'QUw='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'QU0='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'QU4='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'QU8='").next(TokenKind.PrimitiveBinaryValue)); + assertFalse(new UriTokenizer("binary'QUB='").next(TokenKind.PrimitiveBinaryValue)); + + // all cases with two base64 characters (and two fill characters) at the end + assertTrue(new UriTokenizer("BINARY'VGVzdA=='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'U-RnZQ=='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'Yg=='").next(TokenKind.PrimitiveBinaryValue)); + assertTrue(new UriTokenizer("binary'Yw=='").next(TokenKind.PrimitiveBinaryValue)); + + // without optional fill character + assertTrue(new UriTokenizer("binary'T0RhdGE'").next(TokenKind.PrimitiveBinaryValue)); + + // special character '_' (the other, '-', already has been used above) + assertTrue(new UriTokenizer("binary'V_ZydGVy'").next(TokenKind.PrimitiveBinaryValue)); + + wrongToken(TokenKind.PrimitiveBinaryValue, "binary'VGVzdA=='", '+'); + } + + @Test + public void enumValue() { + assertTrue(new UriTokenizer("namespace.name'value'").next(TokenKind.PrimitiveEnumValue)); + assertTrue(new UriTokenizer("namespace.name'flag1,flag2,-3'").next(TokenKind.PrimitiveEnumValue)); + assertFalse(new UriTokenizer("namespace.name'1flag'").next(TokenKind.PrimitiveEnumValue)); + assertFalse(new UriTokenizer("namespace.name'flag1,,flag2'").next(TokenKind.PrimitiveEnumValue)); + assertFalse(new UriTokenizer("namespace.name',value'").next(TokenKind.PrimitiveEnumValue)); + assertFalse(new UriTokenizer("namespace.name'value,'").next(TokenKind.PrimitiveEnumValue)); + assertFalse(new UriTokenizer("namespace.name''").next(TokenKind.PrimitiveEnumValue)); + assertFalse(new UriTokenizer("'1'").next(TokenKind.PrimitiveEnumValue)); + assertFalse(new UriTokenizer("1").next(TokenKind.PrimitiveEnumValue)); + wrongToken(TokenKind.PrimitiveEnumValue, "namespace.name'_1,_2,3'", ';'); + } + + @Test + public void json() { + // Empty string or JSON values are not allowed. + assertFalse(new UriTokenizer("").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("1").next(TokenKind.jsonArrayOrObject)); + + // object with values + assertTrue(new UriTokenizer("{}").next(TokenKind.jsonArrayOrObject)); + assertTrue(new UriTokenizer("{\"name\":0}").next(TokenKind.jsonArrayOrObject)); + assertTrue(new UriTokenizer("{\"name\":true}").next(TokenKind.jsonArrayOrObject)); + assertTrue(new UriTokenizer("{\"name\":false}").next(TokenKind.jsonArrayOrObject)); + assertTrue(new UriTokenizer("{\"name\":null}").next(TokenKind.jsonArrayOrObject)); + assertTrue(new UriTokenizer("{\"name\":\"value\"}").next(TokenKind.jsonArrayOrObject)); + assertTrue(new UriTokenizer("{\"name\":\"value\",\"name2\":null}").next(TokenKind.jsonArrayOrObject)); + + // array with values + assertTrue(new UriTokenizer("[]").next(TokenKind.jsonArrayOrObject)); + assertTrue(new UriTokenizer("[1]").next(TokenKind.jsonArrayOrObject)); + assertTrue(new UriTokenizer("[true]").next(TokenKind.jsonArrayOrObject)); + assertTrue(new UriTokenizer("[false]").next(TokenKind.jsonArrayOrObject)); + assertTrue(new UriTokenizer("[null]").next(TokenKind.jsonArrayOrObject)); + assertTrue(new UriTokenizer("[\"value\"]").next(TokenKind.jsonArrayOrObject)); + assertTrue(new UriTokenizer("[\"\"]").next(TokenKind.jsonArrayOrObject)); + assertTrue(new UriTokenizer("[\"\\b\\t\\f\\r\\nn\\/\\\\x\\uFE4Fu\\\"\"]").next(TokenKind.jsonArrayOrObject)); + assertTrue(new UriTokenizer("[1,2.0,3.4E5]").next(TokenKind.jsonArrayOrObject)); + assertTrue(new UriTokenizer("[\"value\",null]").next(TokenKind.jsonArrayOrObject)); + + // nesting + assertTrue(new UriTokenizer("[{\"name\":\"value\"},{\"name\":\"value2\"}]").next(TokenKind.jsonArrayOrObject)); + assertTrue(new UriTokenizer("{\"name\":{\"name2\":\"value\"}}").next(TokenKind.jsonArrayOrObject)); + + // unbalanced opening and closing + assertFalse(new UriTokenizer("{").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("}").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("[").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("]").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("{]").next(TokenKind.jsonArrayOrObject)); + + // missing values + assertFalse(new UriTokenizer("[1,]").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("[,1]").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("[1,,2]").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("[1,x]").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("{\"name\":1,}").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("{,\"name\":1}").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("{\"name\":1,,\"name2\":2}").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("{\"name\":1,x}").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("{\"name\":1,\"name2\"}").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("{\"name\":1,\"name2\":}").next(TokenKind.jsonArrayOrObject)); + + // wrong JSON strings + assertFalse(new UriTokenizer("[\"a").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("[\"a]").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("[\"a\"\"]").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("[\"\\x\"]").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("[\"\\ux\"]").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("[\"\\u1\"]").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("[\"\\u12x\"]").next(TokenKind.jsonArrayOrObject)); + assertFalse(new UriTokenizer("[\"\\u123x\"]").next(TokenKind.jsonArrayOrObject)); + } + + private void wrongToken(final TokenKind kind, final String value, final char disturbCharacter) { + assertFalse(new UriTokenizer(disturbCharacter + value).next(kind)); + + final UriTokenizer tokenizer = new UriTokenizer(value + disturbCharacter); + assertTrue(tokenizer.next(kind)); + assertEquals(value, tokenizer.getText()); + + // Place the disturbing character at every position in the value string + // and check that this leads to a failed token recognition. + for (int index = 0; index < value.length(); index++) { + assertFalse("Error at index " + index, + new UriTokenizer(value.substring(0, index) + disturbCharacter + value.substring(index + 1)).next(kind)); + } + } +} diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchTokenizerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchTokenizerTest.java index c2a390af8..754768756 100644 --- a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchTokenizerTest.java +++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchTokenizerTest.java @@ -31,7 +31,6 @@ import java.util.Iterator; import java.util.List; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; public class SearchTokenizerTest { diff --git a/lib/server-tecsvc/src/test/java/org/apache/olingo/server/tecsvc/data/DataProviderTest.java b/lib/server-tecsvc/src/test/java/org/apache/olingo/server/tecsvc/data/DataProviderTest.java index 93c3c9a22..6bd646326 100644 --- a/lib/server-tecsvc/src/test/java/org/apache/olingo/server/tecsvc/data/DataProviderTest.java +++ b/lib/server-tecsvc/src/test/java/org/apache/olingo/server/tecsvc/data/DataProviderTest.java @@ -29,7 +29,6 @@ import org.apache.olingo.commons.api.data.Property; import org.apache.olingo.commons.api.edm.Edm; import org.apache.olingo.commons.api.edm.EdmEntityContainer; import org.apache.olingo.commons.api.edm.EdmEntitySet; -import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.edmx.EdmxReference; import org.apache.olingo.server.api.uri.UriParameter; @@ -44,8 +43,7 @@ public class DataProviderTest { private final Edm edm = oData.createServiceMetadata(new EdmTechProvider(), Collections. emptyList()) .getEdm(); - private final EdmEntityContainer entityContainer = edm.getEntityContainer( - new FullQualifiedName("olingo.odata.test1", "Container")); + private final EdmEntityContainer entityContainer = edm.getEntityContainer(); private final EdmEntitySet esAllPrim = entityContainer.getEntitySet("ESAllPrim"); private final EdmEntitySet esAllKey = entityContainer.getEntitySet("ESAllKey"); diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/PreconditionsValidatorTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/PreconditionsValidatorTest.java index 5e3499c3e..bf68fb374 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/PreconditionsValidatorTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/PreconditionsValidatorTest.java @@ -41,7 +41,7 @@ import org.apache.olingo.server.api.uri.UriResourceValue; import org.apache.olingo.server.core.etag.PreconditionsValidator; import org.apache.olingo.server.core.uri.parser.Parser; import org.apache.olingo.server.core.uri.parser.UriParserException; -import org.apache.olingo.server.core.uri.parser.UriParserSemanticException; +import org.apache.olingo.server.core.uri.validator.UriValidationException; import org.apache.olingo.server.tecsvc.provider.EdmTechProvider; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; @@ -49,7 +49,8 @@ import org.mockito.stubbing.Answer; public class PreconditionsValidatorTest { - private static final Edm edm = OData.newInstance().createServiceMetadata( + private static final OData odata = OData.newInstance(); + private static final Edm edm = odata.createServiceMetadata( new EdmTechProvider(), Collections. emptyList()).getEdm(); // -------------- POSITIVE TESTS -------------------------------------------------------------------------------- @@ -140,7 +141,7 @@ public class PreconditionsValidatorTest { @Test public void simpleEntityValueValidationNotActiveForMedia() throws Exception { - final UriInfo uriInfo = new Parser().parseUri("ESMedia(1)/$value", null, null, edm); + final UriInfo uriInfo = new Parser(edm, odata).parseUri("ESMedia(1)/$value", null, null); CustomETagSupport support = mock(CustomETagSupport.class); when(support.hasETag(any(EdmBindingTarget.class))).thenReturn(true); @@ -185,21 +186,17 @@ public class PreconditionsValidatorTest { assertFalse(mustValidate("SINav/NavPropertyETKeyNavOne/$ref", "ESKeyNav")); } - @Test(expected = UriParserSemanticException.class) - public void resourceSegmentAfterActionMustLeadToUriParserException() throws Exception { - mustValidate("ESKeyNav(1)/Namespace1_Alias.BAETTwoKeyNavRTETTwoKeyNav/PropertyInt16", "ESKeyNav"); - } - - @Test(expected = UriParserSemanticException.class) - public void valueMustBeLastSegment() throws Exception { - mustValidate("ESMedia(1)/$value/PropertyInt16", "ESMedia"); + @Test + public void nonResourceMustBeIgnored() throws Exception { + assertFalse(mustValidate("$all", null)); } private boolean mustValidate(final String uri, final String entitySetName) - throws UriParserException, PreconditionException { - final UriInfo uriInfo = new Parser().parseUri(uri, null, null, edm); + throws UriParserException, UriValidationException, PreconditionException { + final UriInfo uriInfo = new Parser(edm, odata).parseUri(uri, null, null); final List parts = uriInfo.getUriResourceParts(); - final boolean isMedia = parts.get(parts.size() - 1) instanceof UriResourceValue + final boolean isMedia = parts.size() >= 2 + && parts.get(parts.size() - 1) instanceof UriResourceValue && parts.get(parts.size() - 2) instanceof UriResourceEntitySet; CustomETagSupport support = mock(CustomETagSupport.class); diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/utils/ContextURLHelperTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/utils/ContextURLHelperTest.java index fe86896f0..92b069ded 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/utils/ContextURLHelperTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/utils/ContextURLHelperTest.java @@ -28,7 +28,6 @@ import org.apache.olingo.commons.api.edm.Edm; import org.apache.olingo.commons.api.edm.EdmEntityContainer; import org.apache.olingo.commons.api.edm.EdmEntitySet; import org.apache.olingo.commons.api.edm.EdmProperty; -import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.edmx.EdmxReference; import org.apache.olingo.server.api.uri.UriParameter; @@ -45,8 +44,7 @@ public class ContextURLHelperTest { private static final Edm edm = OData.newInstance().createServiceMetadata( new EdmTechProvider(), Collections. emptyList()).getEdm(); - private static final EdmEntityContainer entityContainer = edm.getEntityContainer( - new FullQualifiedName("olingo.odata.test1", "Container")); + private static final EdmEntityContainer entityContainer = edm.getEntityContainer(); @Test public void buildSelect() throws Exception { diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/UriHelperTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/UriHelperTest.java index 781f48d5c..d3dba468b 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/UriHelperTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/UriHelperTest.java @@ -25,7 +25,6 @@ import org.apache.olingo.commons.api.data.ValueType; import org.apache.olingo.commons.api.edm.Edm; import org.apache.olingo.commons.api.edm.EdmEntityContainer; import org.apache.olingo.commons.api.edm.EdmEntitySet; -import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.edmx.EdmxReference; import org.apache.olingo.server.api.serializer.SerializerException; @@ -40,8 +39,7 @@ public class UriHelperTest { private static final OData odata = OData.newInstance(); private static final Edm edm = odata.createServiceMetadata( new EdmTechProvider(), Collections. emptyList()).getEdm(); - private static final EdmEntityContainer container = edm.getEntityContainer( - new FullQualifiedName("olingo.odata.test1", "Container")); + private static final EdmEntityContainer container = edm.getEntityContainer(); private static final UriHelper helper = odata.createUriHelper(); private final DataProvider data = new DataProvider(odata, edm); diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/UriResourceImplTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/UriResourceImplTest.java index 27b879d69..a5d0ea3e8 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/UriResourceImplTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/UriResourceImplTest.java @@ -86,7 +86,7 @@ public class UriResourceImplTest { // action import impl = new UriResourceActionImpl(); - EdmActionImport actionImport = edm.getEntityContainer(null).getActionImport("AIRTCTTwoPrimParam"); + EdmActionImport actionImport = edm.getEntityContainer().getActionImport("AIRTCTTwoPrimParam"); impl.setActionImport(actionImport); assertEquals(actionImport, impl.getActionImport()); assertEquals(actionImport.getUnboundAction(), impl.getAction()); @@ -94,7 +94,7 @@ public class UriResourceImplTest { assertEquals("AIRTCTTwoPrimParam", impl.toString()); assertEquals(actionImport.getUnboundAction().getReturnType().getType(), impl.getType()); - actionImport = edm.getEntityContainer(null).getActionImport("AIRT"); + actionImport = edm.getEntityContainer().getActionImport("AIRT"); impl.setActionImport(actionImport); assertFalse(impl.isCollection()); assertNull(impl.getType()); @@ -184,7 +184,7 @@ public class UriResourceImplTest { UriResourceEntitySetImpl impl = new UriResourceEntitySetImpl(); assertEquals(UriResourceKind.entitySet, impl.getKind()); - EdmEntitySet entitySet = edm.getEntityContainer(null).getEntitySet("ESAllPrim"); + EdmEntitySet entitySet = edm.getEntityContainer().getEntitySet("ESAllPrim"); impl.setEntitSet(entitySet); assertEquals("ESAllPrim", impl.toString()); @@ -207,7 +207,7 @@ public class UriResourceImplTest { assertEquals("", impl.toString()); // function - EdmFunction function = edm.getEntityContainer(null).getFunctionImport("FINRTInt16") + EdmFunction function = edm.getEntityContainer().getFunctionImport("FINRTInt16") .getUnboundFunction(Collections. emptyList()); assertNotNull(function); impl.setFunction(function); @@ -219,14 +219,14 @@ public class UriResourceImplTest { // function import impl = new UriResourceFunctionImpl(); - EdmFunctionImport functionImport = edm.getEntityContainer(null).getFunctionImport("FINRTInt16"); + EdmFunctionImport functionImport = edm.getEntityContainer().getFunctionImport("FINRTInt16"); impl.setFunctionImport(functionImport, Collections. emptyList()); assertEquals(functionImport, impl.getFunctionImport()); assertEquals("FINRTInt16", impl.toString()); // function collection impl = new UriResourceFunctionImpl(); - functionImport = edm.getEntityContainer(null).getFunctionImport("FICRTCollESTwoKeyNavParam"); + functionImport = edm.getEntityContainer().getFunctionImport("FICRTCollESTwoKeyNavParam"); assertNotNull(function); UriParameter parameter = new UriParameterImpl().setName("ParameterInt16"); impl.setFunctionImport(functionImport, Collections.singletonList(parameter)); @@ -414,7 +414,7 @@ public class UriResourceImplTest { UriResourceSingletonImpl impl = new UriResourceSingletonImpl(); assertEquals(UriResourceKind.singleton, impl.getKind()); - EdmSingleton singleton = edm.getEntityContainer(null).getSingleton("SINav"); + EdmSingleton singleton = edm.getEntityContainer().getSingleton("SINav"); EdmEntityType entityTypeBaseColl = edm.getEntityType(EntityTypeProvider.nameETBaseTwoKeyNav); impl.setSingleton(singleton); 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 379345ec3..11acedef2 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 @@ -34,12 +34,10 @@ import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.commons.core.Encoder; import org.apache.olingo.server.api.OData; -import org.apache.olingo.server.api.ODataApplicationException; import org.apache.olingo.server.api.edmx.EdmxReference; import org.apache.olingo.server.api.uri.UriInfoKind; import org.apache.olingo.server.api.uri.UriResourceKind; import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind; -import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitException; import org.apache.olingo.server.api.uri.queryoption.expression.MethodKind; import org.apache.olingo.server.core.uri.parser.UriParserException; import org.apache.olingo.server.core.uri.parser.UriParserSemanticException; @@ -934,14 +932,20 @@ public class TestFullResourcePath { } @Test - public void runCrossjoin() throws Exception { + public void crossjoin() throws Exception { testUri.run("$crossjoin(ESKeyNav)") .isKind(UriInfoKind.crossjoin) .isCrossJoinEntityList(Arrays.asList("ESKeyNav")); - testUri.run("$crossjoin(ESKeyNav, ESTwoKeyNav)") + testUri.run("$crossjoin(ESKeyNav,ESTwoKeyNav)") .isKind(UriInfoKind.crossjoin) .isCrossJoinEntityList(Arrays.asList("ESKeyNav", "ESTwoKeyNav")); + + testUri.run("$crossjoin(ESTwoPrim,ESMixPrimCollComp)", + "$filter=ESTwoPrim/PropertyString eq ESMixPrimCollComp/PropertyComp/PropertyString") + .goFilter() + .isBinary(BinaryOperatorKind.EQ) + .is("< eq >"); } @Test @@ -951,6 +955,8 @@ public class TestFullResourcePath { testUri.runEx("$crossjoin()").isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); testUri.runEx("$crossjoin(ESKeyNav, ESTwoKeyNav)/invalid") .isExSyntax(UriParserSyntaxException.MessageKeys.MUST_BE_LAST_SEGMENT); + testUri.runEx("$crossjoin(ESKeyNav)/$ref") + .isExSyntax(UriParserSyntaxException.MessageKeys.MUST_BE_LAST_SEGMENT); } @Test @@ -982,15 +988,16 @@ public class TestFullResourcePath { } @Test - public void runEsNameError() { + public void esNameError() { - testUri.runEx("ESAllPrim/$count/$ref").isExSemantic(MessageKeys.ONLY_FOR_TYPED_PROPERTIES); - testUri.runEx("ESAllPrim/$ref/$count").isExSemantic(MessageKeys.ONLY_FOR_TYPED_PARTS); - testUri.runEx("ESAllPrim/$ref/invalid").isExSemantic(MessageKeys.RESOURCE_PART_ONLY_FOR_TYPED_PARTS); - testUri.runEx("ESAllPrim/$count/invalid").isExSemantic(MessageKeys.RESOURCE_PART_ONLY_FOR_TYPED_PARTS); + testUri.runEx("ESAllPrim/$count/$ref").isExSyntax(UriParserSyntaxException.MessageKeys.MUST_BE_LAST_SEGMENT); + testUri.runEx("ESAllPrim/$ref/$count").isExSyntax(UriParserSyntaxException.MessageKeys.MUST_BE_LAST_SEGMENT); + testUri.runEx("ESAllPrim/$ref/invalid").isExSyntax(UriParserSyntaxException.MessageKeys.MUST_BE_LAST_SEGMENT); + testUri.runEx("ESAllPrim/$count/invalid").isExSyntax(UriParserSyntaxException.MessageKeys.MUST_BE_LAST_SEGMENT); testUri.runEx("ESAllPrim/PropertyString").isExSemantic(MessageKeys.PROPERTY_AFTER_COLLECTION); testUri.runEx("ESAllPrim(1)/whatever").isExSemantic(MessageKeys.PROPERTY_NOT_IN_TYPE); - testUri.runEx("ESAllPrim(PropertyInt16)").isExSemantic(MessageKeys.INVALID_KEY_VALUE); + testUri.runEx("ESAllPrim('1')").isExSemantic(MessageKeys.INVALID_KEY_VALUE); + testUri.runEx("ESAllPrim(PropertyInt16)").isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); testUri.runEx("ESAllPrim(PropertyInt16=)").isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); testUri.runEx("ESAllPrim(PropertyInt16=1,Invalid='1')").isExSemantic(MessageKeys.WRONG_NUMBER_OF_KEY_PROPERTIES); @@ -1023,8 +1030,7 @@ public class TestFullResourcePath { } @Test - public void runResourcePathWithApostrophe() { - // TODO Currently "'" is not allowed in OData identifiers, but the specification allows this character (Unicode Cf) + public void resourcePathWithApostrophe() { testUri.runEx("ESAllPrim'").isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); testUri.runEx("ESAllPrim'InvalidStuff").isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); @@ -1070,7 +1076,7 @@ public class TestFullResourcePath { .isKeyPredicate(0, "PropertyInt16", "0"); testUri.runEx("FICRTCollETMixPrimCollCompTwoParam(ParameterString='1',ParameterInt16=1)(PropertyInt16 eq 0)") - .isExSemantic(MessageKeys.INVALID_KEY_VALUE); + .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); // PropertyInt32 does not exist testUri.runEx("FICRTCollETMixPrimCollCompTwoParam(ParameterString='1',ParameterInt16=1)(PropertyInt32=0)") @@ -1078,7 +1084,7 @@ public class TestFullResourcePath { testUri.runEx("FICRTCollETMixPrimCollCompTwoParam(ParameterString='1',ParameterInt16=1)" + "(PropertyInt16=0,PropertyInt16=1)") - .isExSemantic(MessageKeys.WRONG_NUMBER_OF_KEY_PROPERTIES); + .isExValidation(UriValidationException.MessageKeys.DOUBLE_KEY_PROPERTY); testUri.run("FICRTCollCTTwoPrimTwoParam(ParameterString='1',ParameterInt16=1)") .isKind(UriInfoKind.resource) @@ -1117,13 +1123,13 @@ public class TestFullResourcePath { .isKeyPredicate(1, "PropertyString", "'1'"); testUri.runEx("FICRTCollESTwoKeyNavParam(ParameterInt16=1)(PropertyInt16 eq 1)") - .isExSemantic(MessageKeys.INVALID_KEY_VALUE); + .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); testUri.runEx("FICRTCollESTwoKeyNavParam(ParameterInt16=1)(PropertyInt16=1)") .isExSemantic(MessageKeys.WRONG_NUMBER_OF_KEY_PROPERTIES); testUri.runEx("FICRTCollESTwoKeyNavParam(ParameterInt16=1)(PropertyInt16=1,PropertyInt32=1,PropertyString='1')") - .isExSemantic(MessageKeys.WRONG_NUMBER_OF_KEY_PROPERTIES); + .isExValidation(UriValidationException.MessageKeys.INVALID_KEY_PROPERTY); testUri.runEx("FICRTCollESTwoKeyNavParam(ParameterInt16=1)()") .isExSemantic(MessageKeys.WRONG_NUMBER_OF_KEY_PROPERTIES); @@ -1193,11 +1199,9 @@ public class TestFullResourcePath { testUri.runEx("FICRTCollETMixPrimCollCompTwoParam(ParameterInt16=1,ParameterString='1')", "$skiptoken=5") .isExValidation(UriValidationException.MessageKeys.SYSTEM_QUERY_OPTION_NOT_ALLOWED); - // $search is currently not implemented. Please change this exception if the implementation is done. - // FIXME (151106:mibo): check after finish of OLINGO-568 -// testUri.runEx("FICRTCollETMixPrimCollCompTwoParam(ParameterInt16=1,ParameterString='1')", "$search=test") -// .isExSemantic(MessageKeys.NOT_IMPLEMENTED); - + testUri.runEx("FICRTCollETMixPrimCollCompTwoParam(ParameterInt16=1,ParameterString='1')", "$search=test") + .isExValidation(UriValidationException.MessageKeys.SYSTEM_QUERY_OPTION_NOT_ALLOWED); + testUri.run("ESAllPrim/olingo.odata.test1.BFNESAllPrimRTCTAllPrim()") .isKind(UriInfoKind.resource) .goPath().first() @@ -1296,8 +1300,8 @@ public class TestFullResourcePath { .isKeyPredicate(2, "KeyAlias2", "'3'") .isKeyPredicate(3, "KeyAlias3", "'4'"); - testUri.runEx("ESTwoPrim(wrong)").isExSemantic(MessageKeys.INVALID_KEY_VALUE); - testUri.runEx("ESTwoPrim(PropertyInt16=wrong)").isExSemantic(MessageKeys.INVALID_KEY_VALUE); + testUri.runEx("ESTwoPrim('wrong')").isExSemantic(MessageKeys.INVALID_KEY_VALUE); + testUri.runEx("ESTwoPrim(PropertyInt16='wrong')").isExSemantic(MessageKeys.INVALID_KEY_VALUE); } @Test @@ -1794,7 +1798,7 @@ public class TestFullResourcePath { @Test public void runEsNamePpNpRc() throws Exception { - // checks for using referential constrains to fill missing keys + // checks for using referential constraints to fill missing keys testUri.run("ESKeyNav(1)/NavPropertyETTwoKeyNavMany('2')").goPath() .first() .isEntitySet("ESKeyNav") @@ -2037,7 +2041,6 @@ public class TestFullResourcePath { @Test public void runFunctionImpEs() throws Exception { - /**/ testUri.run("FICRTESMixPrimCollCompTwoParam(ParameterInt16=1,ParameterString='2')") .isKind(UriInfoKind.resource).goPath() .first() @@ -2805,8 +2808,8 @@ public class TestFullResourcePath { } @Test - @Ignore("$search currently not implemented") - public void runDuplicatedSearchExpand() throws UriParserException, UriValidationException { + @Ignore("$search in expand currently not implemented") + public void duplicatedSearchExpand() throws Exception { testUri.runEx("ESKeyNav", "$expand=NavPropertyETKeyNavOne($search=Test;$search=Test)") .isExSyntax(UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION); } @@ -2931,13 +2934,8 @@ public class TestFullResourcePath { testUri.run("$batch") .isKind(UriInfoKind.batch); - testUri.run("$crossjoin(ESKeyNav)") - .isKind(UriInfoKind.crossjoin) - .isCrossJoinEntityList(Arrays.asList("ESKeyNav")); - testUri.runEx("$metadata/$ref").isExSyntax(UriParserSyntaxException.MessageKeys.MUST_BE_LAST_SEGMENT); testUri.runEx("$batch/$ref").isExSyntax(UriParserSyntaxException.MessageKeys.MUST_BE_LAST_SEGMENT); - testUri.runEx("$crossjoin(ESKeyNav)/$ref").isExSyntax(UriParserSyntaxException.MessageKeys.MUST_BE_LAST_SEGMENT); testUri.runEx("$all/$ref").isExSyntax(UriParserSyntaxException.MessageKeys.MUST_BE_LAST_SEGMENT); testUri.runEx("$entity/olingo.odata.test1.ETKeyNav/$ref") .isExSyntax(UriParserSyntaxException.MessageKeys.MUST_BE_LAST_SEGMENT); @@ -3142,7 +3140,7 @@ public class TestFullResourcePath { } @Test - public void testFilter() throws UriParserException { + public void filter() throws Exception { testFilter.runOnETTwoKeyNav("PropertyString") .is("") @@ -3684,7 +3682,7 @@ public class TestFullResourcePath { } @Test - public void testFilterProperties() throws UriParserException { + public void filterProperties() throws Exception { testFilter.runOnETAllPrim("PropertyByte mod 0") .is("< mod <0>>"); @@ -3849,7 +3847,7 @@ public class TestFullResourcePath { } @Test - public void testFilterPMethods() throws ExpressionVisitException, ODataApplicationException, UriParserException { + public void filterPMethods() throws Exception { testFilter.runOnETKeyNav("indexof(PropertyString,'47') eq 5") .is("<,<'47'>)> eq <5>>") @@ -4465,7 +4463,7 @@ public class TestFullResourcePath { } @Test - public void runLamdbaFunctions() throws ExpressionVisitException, ODataApplicationException, UriParserException { + public void lambdaFunctions() throws Exception { testFilter.runOnETKeyNav("any(d:d/PropertyInt16 eq 1)") .is("< eq <1>>>>") @@ -4612,7 +4610,7 @@ public class TestFullResourcePath { } @Test - public void runIsOf() throws ExpressionVisitException, ODataApplicationException, UriParserException { + public void runIsOf() throws Exception { testFilter.runOnETKeyNav("isof(olingo.odata.test1.ETTwoKeyNav)") .is(")>") @@ -4757,7 +4755,7 @@ public class TestFullResourcePath { } @Test - public void testHas() throws ExpressionVisitException, ODataApplicationException, UriParserException { + public void has() throws Exception { testFilter.runOnETMixEnumDefCollComp("PropertyEnumString has olingo.odata.test1.ENString'String1'") .is("< has >>") @@ -5086,7 +5084,7 @@ public class TestFullResourcePath { } @Test - public void testOrderby() throws UriParserException, UnsupportedEncodingException { + public void orderby() throws Exception { testFilter.runOrderByOnETTwoKeyNav("olingo.odata.test1.UFCRTETAllPrimTwoParam(" + "ParameterString=@ParamStringAlias,ParameterInt16=@ParamInt16Alias)/PropertyString eq 'SomeString'") @@ -5429,7 +5427,7 @@ public class TestFullResourcePath { } @Test - public void testSearch() throws Exception { + public void search() throws Exception { testUri.run("ESTwoKeyNav", "$search=abc"); testUri.run("ESTwoKeyNav", "$search=NOT abc"); @@ -5462,6 +5460,9 @@ public class TestFullResourcePath { testUri.run("ESTwoKeyNav", "$search=abc AND (def OR ghi)"); testUri.run("ESTwoKeyNav", "$search=abc AND (def ghi)"); + // search in function-import return value + testUri.run("FICRTCollESTwoKeyNavParam(ParameterInt16=1)", "$search=test"); + // percent encoded characters testUri.run("ESTwoKeyNav", "$search=%41%42%43"); testUri.run("ESTwoKeyNav", "$search=\"100%25\""); @@ -5484,7 +5485,6 @@ public class TestFullResourcePath { /** * https://tools.oasis-open.org/version-control/browse/wsvn/odata/trunk/spec/ABNF/odata-abnf-testcases.xml - * @throws Exception */ @Test public void searchQueryPhraseAbnfTestcases() throws Exception { @@ -5568,10 +5568,10 @@ public class TestFullResourcePath { } @Test - public void testErrors() { + public void errors() { testUri.runEx("FICRTString(wrong1='ABC')/olingo.odata.test1.BFCStringRTESTwoKeyNav()") .isExSemantic(MessageKeys.FUNCTION_NOT_FOUND); - testUri.runEx("FICRTString(wrong1='ABC', wrong2=1)/olingo.odata.test1.BFCStringRTESTwoKeyNav()") + testUri.runEx("FICRTString(wrong1='ABC',wrong2=1)/olingo.odata.test1.BFCStringRTESTwoKeyNav()") .isExSemantic(MessageKeys.FUNCTION_NOT_FOUND); // type filter for entity incompatible @@ -5620,14 +5620,14 @@ public class TestFullResourcePath { // Actions must not be followed by anything. testUri.runEx(ContainerProvider.AIRT_STRING + "/$value") - .isExValidation(UriValidationException.MessageKeys.UNALLOWED_KIND_BEFORE_VALUE); + .isExValidation(UriValidationException.MessageKeys.UNALLOWED_RESOURCE_PATH); testUri.runEx(ContainerProvider.AIRTCT_TWO_PRIM_PARAM + "/PropertyInt16") - .isExSemantic(MessageKeys.RESOURCE_PART_ONLY_FOR_TYPED_PARTS); + .isExValidation(UriValidationException.MessageKeys.UNALLOWED_RESOURCE_PATH); testUri.runEx("ESTwoKeyNav(PropertyInt16=1,PropertyString='2')/" + "olingo.odata.test1.BAETTwoKeyNavRTETTwoKeyNav/olingo.odata.test1.ETTwoKeyNav") - .isExSemantic(MessageKeys.RESOURCE_PART_ONLY_FOR_TYPED_PARTS); + .isExValidation(UriValidationException.MessageKeys.UNALLOWED_RESOURCE_PATH); testUri.runEx("ESTwoKeyNav/olingo.odata.test1.BAESTwoKeyNavRTESTwoKeyNav/$count") - .isExValidation(UriValidationException.MessageKeys.UNALLOWED_KIND_BEFORE_COUNT); + .isExValidation(UriValidationException.MessageKeys.UNALLOWED_RESOURCE_PATH); } @Test @@ -5646,7 +5646,7 @@ public class TestFullResourcePath { @Test public void multipleKeysInResourcePath() throws Exception { // See OLINGO-730 - testUri.runEx("ESAllPrim(32767)(1)(2)").isExSemantic(MessageKeys.WRONG_NUMBER_OF_KEY_PROPERTIES); + testUri.runEx("ESAllPrim(32767)(1)(2)").isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); } @Test @@ -5705,9 +5705,9 @@ public class TestFullResourcePath { @Test public void navigationWithMoreThanOneKey() throws Exception { - testUri.runEx("ESKeyNav(1)/NavPropertyETTwoKeyNavMany(PropertyInt=1,PropertyString='2')" - + "(PropertyInt=1,PropertyString='2')") - .isExSemantic(MessageKeys.WRONG_NUMBER_OF_KEY_PROPERTIES); + testUri.runEx("ESKeyNav(1)/NavPropertyETTwoKeyNavMany(PropertyInt16=1,PropertyString='2')" + + "(PropertyInt16=1,PropertyString='2')") + .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); } @Test @@ -5724,10 +5724,14 @@ public class TestFullResourcePath { testUri.runEx("FICRTETKeyNav()/SINav").isExSemantic(MessageKeys.PROPERTY_NOT_IN_TYPE); testUri.runEx("FICRTETKeyNav()/FICRTString()").isExSemantic(MessageKeys.PROPERTY_NOT_IN_TYPE); testUri.runEx("FICRTETKeyNav()/AIRTString").isExSemantic(MessageKeys.PROPERTY_NOT_IN_TYPE); - testUri.runEx("AIRTESAllPrimParam/ESAllPrim(0)").isExSemantic(MessageKeys.RESOURCE_PART_ONLY_FOR_TYPED_PARTS); - testUri.runEx("AIRTESAllPrimParam/SINav").isExSemantic(MessageKeys.RESOURCE_PART_ONLY_FOR_TYPED_PARTS); - testUri.runEx("AIRTESAllPrimParam/FICRTString()").isExSemantic(MessageKeys.RESOURCE_PART_ONLY_FOR_TYPED_PARTS); - testUri.runEx("AIRTESAllPrimParam/AIRTString").isExSemantic(MessageKeys.RESOURCE_PART_ONLY_FOR_TYPED_PARTS); + testUri.runEx("AIRTESAllPrimParam/ESAllPrim(0)") + .isExValidation(UriValidationException.MessageKeys.UNALLOWED_RESOURCE_PATH); + testUri.runEx("AIRTESAllPrimParam/SINav") + .isExValidation(UriValidationException.MessageKeys.UNALLOWED_RESOURCE_PATH); + testUri.runEx("AIRTESAllPrimParam/FICRTString()") + .isExValidation(UriValidationException.MessageKeys.UNALLOWED_RESOURCE_PATH); + testUri.runEx("AIRTESAllPrimParam/AIRTString") + .isExValidation(UriValidationException.MessageKeys.UNALLOWED_RESOURCE_PATH); } @Test @@ -5737,7 +5741,7 @@ public class TestFullResourcePath { } @Test - public void testFirstResourcePathWithNamespace() { + public void firstResourcePathWithNamespace() { testUri.runEx("olingo.odata.test1.ESAllPrim").isExSemantic(MessageKeys.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT); testUri.runEx("olingo.odata.test1.ESAllPrim(0)").isExSemantic(MessageKeys.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT); testUri.runEx("olingo.odata.test1.FINRTInt16()").isExSemantic(MessageKeys.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT); @@ -5764,7 +5768,7 @@ public class TestFullResourcePath { Mockito.when(entityType.getFullQualifiedName()).thenReturn(nameETNavProp); Mockito.when(entityType.getKeyPredicateNames()).thenReturn(Collections.singletonList(keyPropertyName)); Mockito.when(entityType.getKeyPropertyRefs()).thenReturn(Collections.singletonList(keyPropertyRef)); - Mockito.when(entityType.getProperty(entitySetName)).thenReturn(navProperty); + Mockito.when(entityType.getNavigationProperty(entitySetName)).thenReturn(navProperty); Mockito.when(navProperty.getType()).thenReturn(entityType); EdmEntitySet entitySet = Mockito.mock(EdmEntitySet.class); Mockito.when(entitySet.getName()).thenReturn(entitySetName); @@ -5772,7 +5776,7 @@ public class TestFullResourcePath { EdmEntityContainer container = Mockito.mock(EdmEntityContainer.class); Mockito.when(container.getEntitySet(entitySetName)).thenReturn(entitySet); Edm mockedEdm = Mockito.mock(Edm.class); - Mockito.when(mockedEdm.getEntityContainer(null)).thenReturn(container); + Mockito.when(mockedEdm.getEntityContainer()).thenReturn(container); new TestUriValidator().setEdm(mockedEdm) .run("ESNavProp(1)/ESNavProp(2)/ESNavProp(3)/ESNavProp") .goPath() @@ -5886,11 +5890,11 @@ public class TestFullResourcePath { @Test public void functionsWithComplexParameters() throws Exception { testUri.run("ESTwoKeyNav/olingo.odata.test1.BFCESTwoKeyNavRTStringParam" - + "(ParameterComp=@p1)", "@p1={\"PropertyInt16\":1,\"ProperyString\":\"1\"}") + + "(ParameterComp=@p1)", "@p1={\"PropertyInt16\":1,\"PropertyString\":\"1\"}") .goPath() .at(0).isEntitySet("ESTwoKeyNav") .at(1).isFunction("BFCESTwoKeyNavRTStringParam").isParameterAlias(0, "ParameterComp", "@p1") - .isInAliasToValueMap("@p1", "{\"PropertyInt16\":1,\"ProperyString\":\"1\"}"); + .isInAliasToValueMap("@p1", "{\"PropertyInt16\":1,\"PropertyString\":\"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\\\\}\\\\{\\\\)" @@ -5899,11 +5903,11 @@ public class TestFullResourcePath { + "\\\\(\\\\]\\\\[}"; testUri.run("ESTwoKeyNav/olingo.odata.test1.BFCESTwoKeyNavRTStringParam" - + "(ParameterComp=@p1)", "@p1={\"PropertyInt16\":1,\"ProperyString\":\"" + stringValueEncoded + "\"}") + + "(ParameterComp=@p1)", "@p1={\"PropertyInt16\":1,\"PropertyString\":\"" + stringValueEncoded + "\"}") .goPath() .at(0).isEntitySet("ESTwoKeyNav") .at(1).isFunction("BFCESTwoKeyNavRTStringParam").isParameterAlias(0, "ParameterComp", "@p1") - .isInAliasToValueMap("@p1", "{\"PropertyInt16\":1,\"ProperyString\":\"" + stringValueDecoded + "\"}"); + .isInAliasToValueMap("@p1", "{\"PropertyInt16\":1,\"PropertyString\":\"" + stringValueDecoded + "\"}"); testUri.run("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam" + "(ParameterComp={\"PropertyString\":\"Test\",\"PropertyInt16\":1}) eq 'Test'") @@ -5916,24 +5920,24 @@ public class TestFullResourcePath { .isParameterText(0, "{\"PropertyString\":\"" + stringValueDecoded + "\",\"PropertyInt16\":1}"); testUri.run("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam" - + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":1,\"ProperyString\":\"1\"}"); + + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":1,\"PropertyString\":\"1\"}"); testUri.run("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam" - + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":1,\"ProperyString\":null}") + + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":1,\"PropertyString\":null}") .goFilter().left().isParameterText(0, null); testUri.run("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam" + "(ParameterComp=@p1) eq 0&@p1={}"); testUri.run("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam" - + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":[1,2,3],\"ProperyString\":\"1\"}"); + + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":[1,2,3],\"PropertyString\":\"1\"}"); testUri.run("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam" - + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":[\"1\",\"2\",\"3\"],\"ProperyString\":\"1\"}"); + + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":[\"1\",\"2\",\"3\"],\"PropertyString\":\"1\"}"); testUri.run("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam" + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":[{\"Prop1\":123,\"Prop2\":\"Test\",\"Prop3\":[1,2,3]}," - + "{\"Prop1\":{\"Prop1\":[\"Prop\\\":{]\"]}}],\"ProperyString\":\"1\"}"); + + "{\"Prop1\":{\"Prop1\":[\"Prop\\\":{]\"]}}],\"PropertyString\":\"1\"}"); testUri.run("FINRTByteNineParam(ParameterEnum=null,ParameterDef='x',ParameterComp=@c," + "ParameterETTwoPrim=@c,CollParameterByte=@e,CollParameterEnum=@e,CollParameterDef=@e," @@ -5941,12 +5945,12 @@ public class TestFullResourcePath { "@c={}&@e=[]"); testUri.runEx("ESTwoKeyNav/olingo.odata.test1.BFCESTwoKeyNavRTStringParam" - + "(ParameterComp=@p1)", "@p1={\"PropertyInt16\":1,\"ProperyString\":'1'}") + + "(ParameterComp=@p1)", "@p1={\"PropertyInt16\":1,\"PropertyString\":'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); + .isExSemantic(UriParserSemanticException.MessageKeys.INVALID_KEY_VALUE); testUri.runEx("FICRTCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=null)") .isExValidation(UriValidationException.MessageKeys.MISSING_PARAMETER); @@ -5959,7 +5963,7 @@ public class TestFullResourcePath { testUri.run("FICRTCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test)", "@test='null'"); - testUri.runEx("FICRTCTTwoPrimTwoParam(ParameterInt16=1,ParameterString=@test, UnknownParam=1)", "@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'"); @@ -5974,23 +5978,23 @@ public class TestFullResourcePath { .isExSemantic(UriParserSemanticException.MessageKeys.FUNCTION_IMPORT_NOT_ALLOWED); testUri.runEx("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam" - + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":1,\"ProperyString\":\"1\"") + + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":1,\"PropertyString\":\"1\"") .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); testUri.runEx("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam" - + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":1,\"ProperyString\":\"1\"}}") + + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":1,\"PropertyString\":\"1\"}}") .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); testUri.runEx("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam" - + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":[1,2,3]],\"ProperyString\":\"1\"}") + + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":[1,2,3]],\"PropertyString\":\"1\"}") .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); testUri.runEx("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam" - + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":[1,2,3,\"ProperyString\":\"1\"}") + + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":[1,2,3,\"PropertyString\":\"1\"}") .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); testUri.runEx("ESTwoKeyNav", "$filter=olingo.odata.test1.BFCESTwoKeyNavRTStringParam" - + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":[1,2,3},\"ProperyString\":\"1\"}") + + "(ParameterComp=@p1) eq 0&@p1={\"PropertyInt16\":[1,2,3},\"PropertyString\":\"1\"}") .isExSyntax(UriParserSyntaxException.MessageKeys.SYNTAX); } 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 9011bc4a5..e6612ff16 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 @@ -28,7 +28,6 @@ import org.apache.olingo.server.api.edmx.EdmxReference; import org.apache.olingo.server.api.uri.UriInfoKind; import org.apache.olingo.server.api.uri.UriResourceKind; import org.apache.olingo.server.api.uri.queryoption.expression.MethodKind; -import org.apache.olingo.server.core.uri.parser.UriParserException; import org.apache.olingo.server.core.uri.parser.UriParserSemanticException; import org.apache.olingo.server.core.uri.parser.UriParserSyntaxException; import org.apache.olingo.server.core.uri.testutil.FilterValidator; @@ -186,11 +185,11 @@ public class TestUriParserImpl { .isType(EntityTypeProvider.nameETTwoKeyTwoPrim, false); testUri.runEx(ContainerProvider.AIRT_STRING + "/invalidElement") - .isExSemantic(UriParserSemanticException.MessageKeys.RESOURCE_PART_ONLY_FOR_TYPED_PARTS); + .isExValidation(UriValidationException.MessageKeys.UNALLOWED_RESOURCE_PATH); } @Test - public void runCount() { + public void count() { // count entity set testRes.run("ESAllPrim/$count") @@ -338,7 +337,7 @@ public class TestUriParserImpl { .isKeyPredicate(0, "PropertyInt16", "1"); // with two keys - testRes.run("ESTwoKeyTwoPrim(PropertyInt16=1, PropertyString='ABC')") + testRes.run("ESTwoKeyTwoPrim(PropertyInt16=1,PropertyString='ABC')") .isEntitySet("ESTwoKeyTwoPrim") .isKeyPredicate(0, "PropertyInt16", "1") .isKeyPredicate(1, "PropertyString", "'ABC'"); @@ -562,14 +561,14 @@ public class TestUriParserImpl { } @Test - public void testUnary() throws UriParserException { + public void unary() throws Exception { testFilter.runOnETAllPrim("not PropertyBoolean").isCompr(">"); testFilter.runOnETAllPrim("- PropertyInt16 eq PropertyInt16").isCompr("<<- > eq >"); testFilter.runOnETAllPrim("-PropertyInt16 eq PropertyInt16").isCompr("<<- > eq >"); } @Test - public void testFilterComplexMixedPriority() throws UriParserException { + public void filterComplexMixedPriority() throws Exception { testFilter.runOnETAllPrim("PropertyInt16 or PropertyInt32 and PropertyInt64") .isCompr("< or < and >>"); testFilter.runOnETAllPrim("PropertyInt16 or PropertyInt32 and PropertyInt64 eq PropertyByte") @@ -594,7 +593,7 @@ public class TestUriParserImpl { } @Test - public void testFilterSimpleSameBinaryBinaryBinaryPriority() throws UriParserException { + public void filterSimpleSameBinaryBinaryBinaryPriority() throws Exception { testFilter.runOnETAllPrim("1 add 2 add 3 add 4").isCompr("<<< <1> add <2>> add <3>> add <4>>"); testFilter.runOnETAllPrim("1 add 2 add 3 div 4").isCompr("<< <1> add <2>> add <<3> div <4>>>"); testFilter.runOnETAllPrim("1 add 2 div 3 add 4").isCompr("<< <1> add <<2> div <3>>> add <4>>"); @@ -1101,7 +1100,7 @@ public class TestUriParserImpl { } @Test - public void testGeo() throws UriParserException { + public void geo() throws Exception { testFilter.runOnETAllPrim("geo.distance(PropertySByte,PropertySByte)") .is(",)>") .isMethod(MethodKind.GEODISTANCE, 2); diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ParserTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ParserTest.java index 3d67c481f..f54ad0483 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ParserTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/ParserTest.java @@ -55,7 +55,7 @@ public class ParserTest { EdmEntityType productsType = Mockito.mock(EdmEntityType.class); final FullQualifiedName nameProducts = new FullQualifiedName("NS", "Products"); - Mockito.when(mockEdm.getEntityContainer(null)).thenReturn(container); + Mockito.when(mockEdm.getEntityContainer()).thenReturn(container); Mockito.when(typeCategory.getName()).thenReturn("Category"); Mockito.when(typeCategory.getNamespace()).thenReturn("NS"); Mockito.when(esCategory.getEntityType()).thenReturn(typeCategory); @@ -97,7 +97,7 @@ public class ParserTest { EdmEntityType typeProduct = Mockito.mock(EdmEntityType.class); FullQualifiedName fqnProduct = new FullQualifiedName("NS", "Products"); - Mockito.when(mockEdm.getEntityContainer(null)).thenReturn(container); + Mockito.when(mockEdm.getEntityContainer()).thenReturn(container); Mockito.when(typeCategory.getName()).thenReturn(fqnCategory.getName()); Mockito.when(typeCategory.getNamespace()).thenReturn(fqnCategory.getNamespace()); Mockito.when(typeCategory.getFullQualifiedName()).thenReturn(fqnCategory); 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 309a25f97..0800dd060 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 @@ -28,7 +28,9 @@ import java.util.List; import org.apache.olingo.commons.api.edm.Edm; import org.apache.olingo.commons.api.edm.EdmType; import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.ODataApplicationException; +import org.apache.olingo.server.api.ODataLibraryException; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriInfoKind; import org.apache.olingo.server.api.uri.UriResource; @@ -51,8 +53,10 @@ import org.apache.olingo.server.core.uri.parser.UriParserSemanticException; import org.apache.olingo.server.core.uri.parser.UriParserSyntaxException; import org.apache.olingo.server.core.uri.queryoption.expression.MemberImpl; import org.apache.olingo.server.core.uri.queryoption.expression.MethodImpl; +import org.apache.olingo.server.core.uri.validator.UriValidationException; public class FilterValidator implements TestValidator { + private final OData odata = OData.newInstance(); private Edm edm; private TestValidator invokedByValidator; @@ -62,7 +66,7 @@ public class FilterValidator implements TestValidator { private Expression curExpression; private Expression rootExpression; - private UriParserException exception; + private ODataLibraryException exception; // --- Setup --- public FilterValidator setUriResourcePathValidator(final ResourceValidator uriResourcePathValidator) { @@ -107,15 +111,18 @@ public class FilterValidator implements TestValidator { // --- Execution --- - public FilterValidator runOrderByOnETAllPrim(final String orderBy) throws UriParserException { + public FilterValidator runOrderByOnETAllPrim(final String orderBy) + throws UriParserException, UriValidationException { return runUriOrderBy("ESAllPrim", "$orderby=" + orderBy.trim()); } - public FilterValidator runOrderByOnETTwoKeyNav(final String orderBy) throws UriParserException { + public FilterValidator runOrderByOnETTwoKeyNav(final String orderBy) + throws UriParserException, UriValidationException { return runUriOrderBy("ESTwoKeyNav", "$orderby=" + orderBy.trim()); } - public FilterValidator runOrderByOnETMixEnumDefCollComp(final String orderBy) throws UriParserException { + public FilterValidator runOrderByOnETMixEnumDefCollComp(final String orderBy) + throws UriParserException, UriValidationException { return runUriOrderBy("ESMixEnumDefCollComp", "$orderby=" + orderBy.trim()); } @@ -123,15 +130,17 @@ public class FilterValidator implements TestValidator { return runUriOrderByEx("ESTwoKeyNav", "$orderby=" + orderBy.trim()); } - public FilterValidator runOnETTwoKeyNav(final String filter) throws UriParserException { + public FilterValidator runOnETTwoKeyNav(final String filter) throws UriParserException, UriValidationException { return runUri("ESTwoKeyNav", "$filter=" + filter.trim()); } - public FilterValidator runOnETMixEnumDefCollComp(final String filter) throws UriParserException { + public FilterValidator runOnETMixEnumDefCollComp(final String filter) + throws UriParserException, UriValidationException { return runUri("ESMixEnumDefCollComp", "$filter=" + filter.trim()); } - public FilterValidator runOnETTwoKeyNavSingle(final String filter) throws UriParserException { + public FilterValidator runOnETTwoKeyNavSingle(final String filter) + throws UriParserException, UriValidationException { return runUri("SINav", "$filter=" + filter.trim()); } @@ -139,11 +148,11 @@ public class FilterValidator implements TestValidator { return runUriEx("ESTwoKeyNav", "$filter=" + filter.trim()); } - public FilterValidator runOnETAllPrim(final String filter) throws UriParserException { + public FilterValidator runOnETAllPrim(final String filter) throws UriParserException, UriValidationException { return runUri("ESAllPrim(1)", "$filter=" + filter.trim()); } - public FilterValidator runOnETKeyNav(final String filter) throws UriParserException { + public FilterValidator runOnETKeyNav(final String filter) throws UriParserException, UriValidationException { return runUri("ESKeyNav(1)", "$filter=" + filter.trim()); } @@ -151,35 +160,36 @@ public class FilterValidator implements TestValidator { return runUriEx("ESKeyNav(1)", "$filter=" + filter.trim()); } - public FilterValidator runOnCTTwoPrim(final String filter) throws UriParserException { + public FilterValidator runOnCTTwoPrim(final String filter) throws UriParserException, UriValidationException { return runUri("SINav/PropertyCompTwoPrim", "$filter=" + filter.trim()); } - public FilterValidator runOnString(final String filter) throws UriParserException { + public FilterValidator runOnString(final String filter) throws UriParserException, UriValidationException { return runUri("SINav/PropertyString", "$filter=" + filter.trim()); } - public FilterValidator runOnInt32(final String filter) throws UriParserException { + public FilterValidator runOnInt32(final String filter) throws UriParserException, UriValidationException { return runUri("ESCollAllPrim(1)/CollPropertyInt32", "$filter=" + filter.trim()); } - public FilterValidator runOnDateTimeOffset(final String filter) throws UriParserException { + public FilterValidator runOnDateTimeOffset(final String filter) throws UriParserException, UriValidationException { return runUri("ESCollAllPrim(1)/CollPropertyDateTimeOffset", "$filter=" + filter.trim()); } - public FilterValidator runOnDuration(final String filter) throws UriParserException { + public FilterValidator runOnDuration(final String filter) throws UriParserException, UriValidationException { return runUri("ESCollAllPrim(1)/CollPropertyDuration", "$filter=" + filter.trim()); } - public FilterValidator runOnTimeOfDay(final String filter) throws UriParserException { + public FilterValidator runOnTimeOfDay(final String filter) throws UriParserException, UriValidationException { return runUri("ESCollAllPrim(1)/CollPropertyTimeOfDay", "$filter=" + filter.trim()); } - public FilterValidator runUri(final String path, final String query) throws UriParserException { - Parser parser = new Parser(); + public FilterValidator runUri(final String path, final String query) + throws UriParserException, UriValidationException { + Parser parser = new Parser(edm, odata); UriInfo uriInfo = null; - uriInfo = parser.parseUri(path, query, null, edm); + uriInfo = parser.parseUri(path, query, null); if (uriInfo.getKind() != UriInfoKind.resource) { fail("Filtervalidator can only be used on resourcePaths"); @@ -193,19 +203,19 @@ public class FilterValidator implements TestValidator { public FilterValidator runUriEx(final String path, final String query) { exception = null; try { - new Parser().parseUri(path, query, null, edm); + new Parser(edm, odata).parseUri(path, query, null); fail("Expected exception not thrown."); } catch (final UriParserException e) { exception = e; + } catch (final UriValidationException e) { + exception = e; } return this; } - public FilterValidator runUriOrderBy(final String path, final String query) throws UriParserException { - Parser parser = new Parser(); - UriInfo uriInfo = null; - - uriInfo = parser.parseUri(path, query, null, edm); + public FilterValidator runUriOrderBy(final String path, final String query) + throws UriParserException, UriValidationException { + final UriInfo uriInfo = new Parser(edm, odata).parseUri(path, query, null); if (uriInfo.getKind() != UriInfoKind.resource) { fail("Filtervalidator can only be used on resourcePaths"); @@ -218,10 +228,12 @@ public class FilterValidator implements TestValidator { public FilterValidator runUriOrderByEx(final String path, final String query) { exception = null; try { - new Parser().parseUri(path, query, null, edm); + new Parser(edm, odata).parseUri(path, query, null); fail("Expected exception not thrown."); } catch (final UriParserException e) { exception = e; + } catch (final UriValidationException e) { + exception = e; } return this; } diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/ParserWithLogging.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/ParserWithLogging.java index 6b2b0df52..5ebc57e3f 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/ParserWithLogging.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/ParserWithLogging.java @@ -20,6 +20,8 @@ package org.apache.olingo.server.core.uri.testutil; import org.antlr.v4.runtime.DefaultErrorStrategy; import org.antlr.v4.runtime.DiagnosticErrorListener; +import org.apache.olingo.commons.api.edm.Edm; +import org.apache.olingo.server.api.OData; import org.apache.olingo.server.core.uri.antlr.UriParserParser; import org.apache.olingo.server.core.uri.parser.Parser; @@ -27,7 +29,8 @@ public class ParserWithLogging extends Parser { TestErrorLogger errorCollector1; TestErrorLogger errorCollector2; - public ParserWithLogging() { + public ParserWithLogging(final Edm edm, final OData odata) { + super(edm, odata); errorCollector1 = new TestErrorLogger("Stage 1", 1); errorCollector2 = new TestErrorLogger("Stage 2", 1); } diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/ResourceValidator.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/ResourceValidator.java index 994b6b285..d70b204be 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/ResourceValidator.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/ResourceValidator.java @@ -30,6 +30,8 @@ import org.apache.olingo.commons.api.edm.EdmElement; import org.apache.olingo.commons.api.edm.EdmType; import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.http.HttpMethod; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.ODataLibraryException; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriInfoKind; import org.apache.olingo.server.api.uri.UriInfoResource; @@ -50,11 +52,11 @@ import org.apache.olingo.server.api.uri.queryoption.ExpandOption; import org.apache.olingo.server.api.uri.queryoption.SelectItem; import org.apache.olingo.server.api.uri.queryoption.SelectOption; import org.apache.olingo.server.core.uri.UriResourceWithKeysImpl; -import org.apache.olingo.server.core.uri.parser.UriParserException; import org.apache.olingo.server.core.uri.validator.UriValidationException; import org.apache.olingo.server.core.uri.validator.UriValidator; public class ResourceValidator implements TestValidator { + private final OData odata = OData.newInstance(); private Edm edm; private TestValidator invokedBy; private UriInfo uriInfo = null; @@ -83,13 +85,13 @@ public class ResourceValidator implements TestValidator { // --- Execution --- public ResourceValidator run(final String path) { - ParserWithLogging testParser = new ParserWithLogging(); + ParserWithLogging testParser = new ParserWithLogging(edm, odata); UriInfo uriInfoTmp = null; uriPathInfo = null; try { - uriInfoTmp = testParser.parseUri(path, null, null, edm); - } catch (final UriParserException e) { + uriInfoTmp = testParser.parseUri(path, null, null); + } catch (final ODataLibraryException e) { fail("Exception occurred while parsing the URI: " + path + "\n" + " Message: " + e.getMessage()); } @@ -279,7 +281,8 @@ public class ResourceValidator implements TestValidator { // input parameter type may be null in order to assert that the collectionTypeFilter is not set EdmType actualType = uriPathInfoKeyPred.getTypeFilterOnCollection(); - assertEquals(expectedType, expectedType == null ? actualType : actualType.getFullQualifiedName()); + assertEquals(expectedType, + expectedType == null || actualType == null ? actualType : actualType.getFullQualifiedName()); return this; } diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/TestUriValidator.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/TestUriValidator.java index 0d5fb4ae1..1dbe62b11 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/TestUriValidator.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/TestUriValidator.java @@ -28,6 +28,7 @@ import org.apache.olingo.commons.api.edm.Edm; import org.apache.olingo.commons.api.edm.EdmType; import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.http.HttpMethod; +import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.ODataLibraryException; import org.apache.olingo.server.api.uri.UriInfo; import org.apache.olingo.server.api.uri.UriInfoKind; @@ -44,6 +45,7 @@ import org.apache.olingo.server.core.uri.validator.UriValidationException; import org.apache.olingo.server.core.uri.validator.UriValidator; public class TestUriValidator implements TestValidator { + private final OData odata = OData.newInstance(); private Edm edm; private UriInfo uriInfo; @@ -62,17 +64,14 @@ public class TestUriValidator implements TestValidator { public TestUriValidator run(final String path, final String query) throws UriParserException, UriValidationException { - Parser parser = new Parser(); - UriValidator validator = new UriValidator(); - - uriInfo = parser.parseUri(path, query, null, edm); - validator.validate(uriInfo, HttpMethod.GET); + uriInfo = new Parser(edm, odata).parseUri(path, query, null); + new UriValidator().validate(uriInfo, HttpMethod.GET); return this; } public TestUriValidator run(final String path, final String query, final String fragment) throws UriParserException, UriValidationException { - uriInfo = new Parser().parseUri(path, query, fragment, edm); + uriInfo = new Parser(edm, odata).parseUri(path, query, fragment); new UriValidator().validate(uriInfo, HttpMethod.GET); return this; } @@ -82,10 +81,9 @@ public class TestUriValidator implements TestValidator { } public TestUriValidator runEx(final String path, final String query) { - Parser parser = new Parser(); uriInfo = null; try { - uriInfo = parser.parseUri(path, query, null, edm); + uriInfo = new Parser(edm, odata).parseUri(path, query, null); new UriValidator().validate(uriInfo, HttpMethod.GET); fail("Exception expected"); } catch (UriParserException e) { diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/validator/UriValidatorTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/validator/UriValidatorTest.java index 0e8b2e6bc..868f9b8d0 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/validator/UriValidatorTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/validator/UriValidatorTest.java @@ -323,7 +323,8 @@ public class UriValidatorTest { { URI_ACTION_ES, QO_ID } }; - private static final Edm edm = OData.newInstance().createServiceMetadata( + private static final OData odata = OData.newInstance(); + private static final Edm edm = odata.createServiceMetadata( new EdmTechProvider(), Collections. emptyList()).getEdm(); @Test @@ -419,24 +420,24 @@ public class UriValidatorTest { public void checkKeys() throws Exception { final TestUriValidator testUri = new TestUriValidator().setEdm(edm); - testUri.run("ESTwoKeyNav(PropertyInt16=1, PropertyString='abc')"); + testUri.run("ESTwoKeyNav(PropertyInt16=1,PropertyString='abc')"); - testUri.runEx("ESTwoKeyNav(xxx=1, yyy='abc')") + testUri.runEx("ESTwoKeyNav(xxx=1,yyy='abc')") .isExValidation(UriValidationException.MessageKeys.INVALID_KEY_PROPERTY); testUri.runEx("ESCollAllPrim(null)").isExValidation(UriValidationException.MessageKeys.INVALID_KEY_PROPERTY); testUri.runEx("ESAllPrim(PropertyInt16='1')") - .isExValidation(UriValidationException.MessageKeys.INVALID_KEY_PROPERTY); + .isExSemantic(UriParserSemanticException.MessageKeys.INVALID_KEY_VALUE); testUri.runEx("ESAllPrim(12345678901234567890)") .isExValidation(UriValidationException.MessageKeys.INVALID_KEY_PROPERTY); testUri.runEx("ESTwoKeyNav(PropertyInt16=1,PropertyString=1)") - .isExValidation(UriValidationException.MessageKeys.INVALID_KEY_PROPERTY); + .isExSemantic(UriParserSemanticException.MessageKeys.INVALID_KEY_VALUE); testUri.runEx("ESTwoKeyNav(PropertyInt16=1,PropertyInt16=1)") .isExValidation(UriValidationException.MessageKeys.DOUBLE_KEY_PROPERTY); testUri.runEx("ESAllPrim(0)/NavPropertyETTwoPrimMany(xxx=42)") .isExValidation(UriValidationException.MessageKeys.INVALID_KEY_PROPERTY); testUri.runEx("ESAllPrim(0)/NavPropertyETTwoPrimMany(PropertyInt16='1')") - .isExValidation(UriValidationException.MessageKeys.INVALID_KEY_PROPERTY); + .isExSemantic(UriParserSemanticException.MessageKeys.INVALID_KEY_VALUE); } @Test @@ -499,7 +500,7 @@ public class UriValidatorTest { private void validate(final String path, final String query, final HttpMethod method) { try { - new UriValidator().validate(new Parser().parseUri(path, query, null, edm), method); + new UriValidator().validate(new Parser(edm, odata).parseUri(path, query, null), method); } catch (final UriParserException e) { fail("Failed for uri: " + path + '?' + query); } catch (final UriValidationException e) { @@ -510,7 +511,7 @@ public class UriValidatorTest { private void validateWrong(final String path, final String query, final HttpMethod method, final UriValidationException.MessageKeys expectedMessageKey) { try { - new UriValidator().validate(new Parser().parseUri(path, query, null, edm), method); + new UriValidator().validate(new Parser(edm, odata).parseUri(path, query, null), method); fail("Validation Exception not thrown: " + method + ' ' + path + '?' + query); } catch (final UriParserException e) { fail("Wrong Exception thrown: " + method + ' ' + path + '?' + query); From 83f11b926da1e52c09b0bc8320eec251946f4952 Mon Sep 17 00:00:00 2001 From: Christian Holzer Date: Tue, 8 Dec 2015 10:20:59 +0100 Subject: [PATCH 2/2] [OLINGO-833] Replace unicode 6.x characters in fit-test --- .../olingo/fit/tecsvc/client/SystemQueryOptionITCase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/SystemQueryOptionITCase.java b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/SystemQueryOptionITCase.java index 3d0df1075..bb7fd31ef 100644 --- a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/SystemQueryOptionITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/SystemQueryOptionITCase.java @@ -320,7 +320,7 @@ public class SystemQueryOptionITCase extends AbstractParamTecSvcITCase { .getEntitySetRequest(getClient().newURIBuilder(SERVICE_URI) .appendEntitySetSegment(ES_ALL_PRIM) .search("\"This is a \\\"$imple\\\"\\\\Phras~\" AND " - + "AnUnicodeWordLl\u01E3Lm\u02B5Lo\u1BE4Lt\u01F2Lu\u03D3Nl\u216F") + + "AnUnicodeWordLl\u01E3Lm\u02B5Lo\u00AALt\u01F2Lu\u03D3Nl\u216F") .build()); setCookieHeader(request); final ODataRetrieveResponse response = request.execute();