[OLINGO-884] Allow PUT on navigation for media resource values
Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/73f46510 Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/73f46510 Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/73f46510 Branch: refs/heads/OLINGO-856_ODataHandlerInAPI Commit: 73f465102a8ea74a4ca6eb3ecc0c8fffbcc3a039 Parents: 0879bfb Author: Christian Amend <[email protected]> Authored: Wed Feb 24 15:26:52 2016 +0100 Committer: Christian Amend <[email protected]> Committed: Wed Feb 24 15:26:52 2016 +0100 ---------------------------------------------------------------------- .../olingo/server/core/ODataDispatcher.java | 92 +++++++-------- .../core/uri/parser/ResourcePathParser.java | 30 ++++- .../uri/parser/UriParserSemanticException.java | 4 +- .../server-core-exceptions-i18n.properties | 1 + .../olingo/server/core/ODataHandlerTest.java | 54 ++++++++- .../core/uri/parser/TestFullResourcePath.java | 113 ++++++++++--------- .../core/uri/testutil/TestUriValidator.java | 1 + 7 files changed, 189 insertions(+), 106 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/73f46510/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataDispatcher.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataDispatcher.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataDispatcher.java index 4971c8e..ceacd10 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataDispatcher.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataDispatcher.java @@ -6,9 +6,9 @@ * 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 @@ -83,26 +83,26 @@ public class ODataDispatcher { } public void dispatch(final ODataRequest request, final ODataResponse response) throws ODataApplicationException, - ODataLibraryException { + ODataLibraryException { switch (uriInfo.getKind()) { case metadata: checkMethod(request.getMethod(), HttpMethod.GET); final ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, handler.getCustomContentTypeSupport(), RepresentationType.METADATA); handler.selectProcessor(MetadataProcessor.class) - .readMetadata(request, response, uriInfo, requestedContentType); + .readMetadata(request, response, uriInfo, requestedContentType); break; case service: checkMethod(request.getMethod(), HttpMethod.GET); if ("".equals(request.getRawODataPath())) { handler.selectProcessor(RedirectProcessor.class) - .redirect(request, response); + .redirect(request, response); } else { final ContentType serviceContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, handler.getCustomContentTypeSupport(), RepresentationType.SERVICE); handler.selectProcessor(ServiceDocumentProcessor.class) - .readServiceDocument(request, response, uriInfo, serviceContentType); + .readServiceDocument(request, response, uriInfo, serviceContentType); } break; @@ -113,7 +113,7 @@ public class ODataDispatcher { case batch: checkMethod(request.getMethod(), HttpMethod.POST); new BatchHandler(handler, handler.selectProcessor(BatchProcessor.class)) - .process(request, response, true); + .process(request, response, true); break; default: @@ -142,7 +142,7 @@ public class ODataDispatcher { case entitySet: case navigationProperty: handleEntityDispatching(request, response, - ((UriResourcePartTyped) lastPathSegment).isCollection(), isMedia(lastPathSegment)); + ((UriResourcePartTyped) lastPathSegment).isCollection(), isEntityOrNavigationMedia(lastPathSegment)); break; case count: @@ -211,7 +211,7 @@ public class ODataDispatcher { final EdmReturnType returnType = action.getReturnType(); if (returnType == null) { handler.selectProcessor(ActionVoidProcessor.class) - .processActionVoid(request, response, uriInfo, requestFormat); + .processActionVoid(request, response, uriInfo, requestFormat); } else { final boolean isCollection = returnType.isCollection(); ContentType responseFormat; @@ -222,10 +222,10 @@ public class ODataDispatcher { isCollection ? RepresentationType.COLLECTION_ENTITY : RepresentationType.ENTITY); if (isCollection) { handler.selectProcessor(ActionEntityCollectionProcessor.class) - .processActionEntityCollection(request, response, uriInfo, requestFormat, responseFormat); + .processActionEntityCollection(request, response, uriInfo, requestFormat, responseFormat); } else { handler.selectProcessor(ActionEntityProcessor.class) - .processActionEntity(request, response, uriInfo, requestFormat, responseFormat); + .processActionEntity(request, response, uriInfo, requestFormat, responseFormat); } break; @@ -235,10 +235,10 @@ public class ODataDispatcher { isCollection ? RepresentationType.COLLECTION_PRIMITIVE : RepresentationType.PRIMITIVE); if (isCollection) { handler.selectProcessor(ActionPrimitiveCollectionProcessor.class) - .processActionPrimitiveCollection(request, response, uriInfo, requestFormat, responseFormat); + .processActionPrimitiveCollection(request, response, uriInfo, requestFormat, responseFormat); } else { handler.selectProcessor(ActionPrimitiveProcessor.class) - .processActionPrimitive(request, response, uriInfo, requestFormat, responseFormat); + .processActionPrimitive(request, response, uriInfo, requestFormat, responseFormat); } break; @@ -248,10 +248,10 @@ public class ODataDispatcher { isCollection ? RepresentationType.COLLECTION_COMPLEX : RepresentationType.COMPLEX); if (isCollection) { handler.selectProcessor(ActionComplexCollectionProcessor.class) - .processActionComplexCollection(request, response, uriInfo, requestFormat, responseFormat); + .processActionComplexCollection(request, response, uriInfo, requestFormat, responseFormat); } else { handler.selectProcessor(ActionComplexProcessor.class) - .processActionComplex(request, response, uriInfo, requestFormat, responseFormat); + .processActionComplex(request, response, uriInfo, requestFormat, responseFormat); } break; @@ -273,13 +273,13 @@ public class ODataDispatcher { final ContentType responseFormat = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, handler.getCustomContentTypeSupport(), RepresentationType.COLLECTION_REFERENCE); handler.selectProcessor(ReferenceCollectionProcessor.class) - .readReferenceCollection(request, response, uriInfo, responseFormat); + .readReferenceCollection(request, response, uriInfo, responseFormat); } else if (isCollection && httpMethod == HttpMethod.POST) { final ContentType requestFormat = getSupportedContentType(request.getHeader(HttpHeader.CONTENT_TYPE), RepresentationType.REFERENCE, true); handler.selectProcessor(ReferenceProcessor.class) - .createReference(request, response, uriInfo, requestFormat); + .createReference(request, response, uriInfo, requestFormat); } else if (!isCollection && httpMethod == HttpMethod.GET) { final ContentType responseFormat = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), @@ -290,11 +290,11 @@ public class ODataDispatcher { final ContentType requestFormat = getSupportedContentType(request.getHeader(HttpHeader.CONTENT_TYPE), RepresentationType.REFERENCE, true); handler.selectProcessor(ReferenceProcessor.class) - .updateReference(request, response, uriInfo, requestFormat); + .updateReference(request, response, uriInfo, requestFormat); } else if (httpMethod == HttpMethod.DELETE) { handler.selectProcessor(ReferenceProcessor.class) - .deleteReference(request, response, uriInfo); + .deleteReference(request, response, uriInfo); } else { throwMethodNotAllowed(httpMethod); @@ -303,6 +303,7 @@ public class ODataDispatcher { private void handleValueDispatching(final ODataRequest request, final ODataResponse response, final int lastPathSegmentIndex) throws ODataApplicationException, ODataLibraryException { + //The URI Parser already checked if $value is allowed here so we only have to dispatch to the correct processor final HttpMethod method = request.getMethod(); final UriResource resource = uriInfo.getUriResourceParts().get(lastPathSegmentIndex - 1); if (resource instanceof UriResourceProperty @@ -336,18 +337,20 @@ public class ODataDispatcher { } } else { if (method == HttpMethod.GET) { + //This can be a GET on an EntitySet, Navigation or Function final ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, handler.getCustomContentTypeSupport(), RepresentationType.MEDIA); handler.selectProcessor(MediaEntityProcessor.class) .readMediaEntity(request, response, uriInfo, requestedContentType); - } else if (method == HttpMethod.PUT && resource instanceof UriResourceEntitySet) { + //PUT and DELETE can only be called on EntitySets or Navigation properties which are media resources + } else if (method == HttpMethod.PUT && isEntityOrNavigationMedia(resource)) { validatePreconditions(request, true); final ContentType requestFormat = ContentType.parse(request.getHeader(HttpHeader.CONTENT_TYPE)); final ContentType responseFormat = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, handler.getCustomContentTypeSupport(), RepresentationType.ENTITY); handler.selectProcessor(MediaEntityProcessor.class) .updateMediaEntity(request, response, uriInfo, requestFormat, responseFormat); - } else if (method == HttpMethod.DELETE && resource instanceof UriResourceEntitySet) { + } else if (method == HttpMethod.DELETE && isEntityOrNavigationMedia(resource)) { validatePreconditions(request, true); handler.selectProcessor(MediaEntityProcessor.class) .deleteMediaEntity(request, response, uriInfo); @@ -367,10 +370,10 @@ public class ODataDispatcher { request, handler.getCustomContentTypeSupport(), complexRepresentationType); if (isCollection) { handler.selectProcessor(ComplexCollectionProcessor.class) - .readComplexCollection(request, response, uriInfo, requestedContentType); + .readComplexCollection(request, response, uriInfo, requestedContentType); } else { handler.selectProcessor(ComplexProcessor.class) - .readComplex(request, response, uriInfo, requestedContentType); + .readComplex(request, response, uriInfo, requestedContentType); } } else if (method == HttpMethod.PUT || method == HttpMethod.PATCH) { validatePreconditions(request, false); @@ -380,19 +383,19 @@ public class ODataDispatcher { request, handler.getCustomContentTypeSupport(), complexRepresentationType); if (isCollection) { handler.selectProcessor(ComplexCollectionProcessor.class) - .updateComplexCollection(request, response, uriInfo, requestFormat, responseFormat); + .updateComplexCollection(request, response, uriInfo, requestFormat, responseFormat); } else { handler.selectProcessor(ComplexProcessor.class) - .updateComplex(request, response, uriInfo, requestFormat, responseFormat); + .updateComplex(request, response, uriInfo, requestFormat, responseFormat); } } else if (method == HttpMethod.DELETE) { validatePreconditions(request, false); if (isCollection) { handler.selectProcessor(ComplexCollectionProcessor.class) - .deleteComplexCollection(request, response, uriInfo); + .deleteComplexCollection(request, response, uriInfo); } else { handler.selectProcessor(ComplexProcessor.class) - .deleteComplex(request, response, uriInfo); + .deleteComplex(request, response, uriInfo); } } else { throwMethodNotAllowed(method); @@ -409,10 +412,10 @@ public class ODataDispatcher { request, handler.getCustomContentTypeSupport(), representationType); if (isCollection) { handler.selectProcessor(PrimitiveCollectionProcessor.class) - .readPrimitiveCollection(request, response, uriInfo, requestedContentType); + .readPrimitiveCollection(request, response, uriInfo, requestedContentType); } else { handler.selectProcessor(PrimitiveProcessor.class) - .readPrimitive(request, response, uriInfo, requestedContentType); + .readPrimitive(request, response, uriInfo, requestedContentType); } } else if (method == HttpMethod.PUT || method == HttpMethod.PATCH) { validatePreconditions(request, false); @@ -422,19 +425,19 @@ public class ODataDispatcher { request, handler.getCustomContentTypeSupport(), representationType); if (isCollection) { handler.selectProcessor(PrimitiveCollectionProcessor.class) - .updatePrimitiveCollection(request, response, uriInfo, requestFormat, responseFormat); + .updatePrimitiveCollection(request, response, uriInfo, requestFormat, responseFormat); } else { handler.selectProcessor(PrimitiveProcessor.class) - .updatePrimitive(request, response, uriInfo, requestFormat, responseFormat); + .updatePrimitive(request, response, uriInfo, requestFormat, responseFormat); } } else if (method == HttpMethod.DELETE) { validatePreconditions(request, false); if (isCollection) { handler.selectProcessor(PrimitiveCollectionProcessor.class) - .deletePrimitiveCollection(request, response, uriInfo); + .deletePrimitiveCollection(request, response, uriInfo); } else { handler.selectProcessor(PrimitiveProcessor.class) - .deletePrimitive(request, response, uriInfo); + .deletePrimitive(request, response, uriInfo); } } else { throwMethodNotAllowed(method); @@ -449,15 +452,15 @@ public class ODataDispatcher { || resource instanceof UriResourceFunction && ((UriResourceFunction) resource).getType().getKind() == EdmTypeKind.ENTITY) { handler.selectProcessor(CountEntityCollectionProcessor.class) - .countEntityCollection(request, response, uriInfo); + .countEntityCollection(request, response, uriInfo); } else if (resource instanceof UriResourcePrimitiveProperty || resource instanceof UriResourceFunction && ((UriResourceFunction) resource).getType().getKind() == EdmTypeKind.PRIMITIVE) { handler.selectProcessor(CountPrimitiveCollectionProcessor.class) - .countPrimitiveCollection(request, response, uriInfo); + .countPrimitiveCollection(request, response, uriInfo); } else { handler.selectProcessor(CountComplexCollectionProcessor.class) - .countComplexCollection(request, response, uriInfo); + .countComplexCollection(request, response, uriInfo); } } @@ -469,19 +472,19 @@ public class ODataDispatcher { final ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, handler.getCustomContentTypeSupport(), RepresentationType.COLLECTION_ENTITY); handler.selectProcessor(EntityCollectionProcessor.class) - .readEntityCollection(request, response, uriInfo, requestedContentType); + .readEntityCollection(request, response, uriInfo, requestedContentType); } else if (method == HttpMethod.POST) { final ContentType responseFormat = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, handler.getCustomContentTypeSupport(), RepresentationType.ENTITY); if (isMedia) { final ContentType requestFormat = ContentType.parse(request.getHeader(HttpHeader.CONTENT_TYPE)); handler.selectProcessor(MediaEntityProcessor.class) - .createMediaEntity(request, response, uriInfo, requestFormat, responseFormat); + .createMediaEntity(request, response, uriInfo, requestFormat, responseFormat); } else { final ContentType requestFormat = getSupportedContentType(request.getHeader(HttpHeader.CONTENT_TYPE), RepresentationType.ENTITY, true); handler.selectProcessor(EntityProcessor.class) - .createEntity(request, response, uriInfo, requestFormat, responseFormat); + .createEntity(request, response, uriInfo, requestFormat, responseFormat); } } else { throwMethodNotAllowed(method); @@ -491,7 +494,7 @@ public class ODataDispatcher { final ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, handler.getCustomContentTypeSupport(), RepresentationType.ENTITY); handler.selectProcessor(EntityProcessor.class) - .readEntity(request, response, uriInfo, requestedContentType); + .readEntity(request, response, uriInfo, requestedContentType); } else if (method == HttpMethod.PUT || method == HttpMethod.PATCH) { validatePreconditions(request, false); final ContentType requestFormat = getSupportedContentType(request.getHeader(HttpHeader.CONTENT_TYPE), @@ -499,11 +502,11 @@ public class ODataDispatcher { final ContentType responseFormat = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(), request, handler.getCustomContentTypeSupport(), RepresentationType.ENTITY); handler.selectProcessor(EntityProcessor.class) - .updateEntity(request, response, uriInfo, requestFormat, responseFormat); + .updateEntity(request, response, uriInfo, requestFormat, responseFormat); } else if (method == HttpMethod.DELETE) { validatePreconditions(request, false); handler.selectProcessor(isMedia ? MediaEntityProcessor.class : EntityProcessor.class) - .deleteEntity(request, response, uriInfo); + .deleteEntity(request, response, uriInfo); } else { throwMethodNotAllowed(method); } @@ -537,7 +540,7 @@ public class ODataDispatcher { private ContentType getSupportedContentType(final String contentTypeHeader, final RepresentationType representationType, final boolean mustNotBeNull) - throws ODataHandlerException, ContentNegotiatorException { + throws ODataHandlerException, ContentNegotiatorException { if (contentTypeHeader == null) { if (mustNotBeNull) { throw new ODataHandlerException(contentTypeHeader, ODataHandlerException.MessageKeys.MISSING_CONTENT_TYPE); @@ -555,7 +558,8 @@ public class ODataDispatcher { return contentType; } - private boolean isMedia(final UriResource pathSegment) { + private boolean isEntityOrNavigationMedia(final UriResource pathSegment) { + //This method MUST NOT check if the resource is of type function since these are handled differently return pathSegment instanceof UriResourceEntitySet && ((UriResourceEntitySet) pathSegment).getEntityType().hasStream() || pathSegment instanceof UriResourceNavigation http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/73f46510/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java index 8d6d52d..5c280c8 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java @@ -6,9 +6,9 @@ * 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 @@ -39,6 +39,9 @@ import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.edm.constants.EdmTypeKind; import org.apache.olingo.server.api.uri.UriParameter; import org.apache.olingo.server.api.uri.UriResource; +import org.apache.olingo.server.api.uri.UriResourceEntitySet; +import org.apache.olingo.server.api.uri.UriResourceFunction; +import org.apache.olingo.server.api.uri.UriResourceNavigation; import org.apache.olingo.server.api.uri.UriResourcePartTyped; import org.apache.olingo.server.api.uri.queryoption.AliasQueryOption; import org.apache.olingo.server.core.uri.UriResourceActionImpl; @@ -120,7 +123,7 @@ public class ResourcePathParser { tokenizer = new UriTokenizer(pathSegment); ParserHelper.requireNext(tokenizer, TokenKind.CROSSJOIN); ParserHelper.requireNext(tokenizer, TokenKind.OPEN); - // At least one entity-set name is mandatory. Try to fetch all. + // At least one entity-set name is mandatory. Try to fetch all. List<String> entitySetNames = new ArrayList<String>(); do { ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier); @@ -153,6 +156,7 @@ public class ResourcePathParser { ParserHelper.requireTokenEnd(tokenizer); requireTyped(previous, "$value"); if (!((UriResourcePartTyped) previous).isCollection()) { + requireMediaResourceInCaseOfEntity(previous); return new UriResourceValueImpl(); } else { throw new UriParserSemanticException("$value is only allowed on typed path segments.", @@ -160,6 +164,26 @@ public class ResourcePathParser { } } + private void requireMediaResourceInCaseOfEntity(UriResource resource) throws UriParserSemanticException { + // If the resource is an entity or navigatio + if (resource instanceof UriResourceEntitySet && !((UriResourceEntitySet) resource).getEntityType().hasStream() + || resource instanceof UriResourceNavigation + && !((EdmEntityType) ((UriResourceNavigation) resource).getType()).hasStream()) { + throw new UriParserSemanticException("$value on entity is only allowed on media resources.", + UriParserSemanticException.MessageKeys.NOT_A_MEDIA_RESOURCE, resource.getSegmentValue()); + } + + // Functions can also deliver an entity. In this case we have to check if the returned entity is a media resource + if (resource instanceof UriResourceFunction) { + EdmType returnType = ((UriResourceFunction) resource).getFunction().getReturnType().getType(); + //Collection check is above so not needed here + if (returnType instanceof EdmEntityType && !((EdmEntityType) returnType).hasStream()) { + throw new UriParserSemanticException("$value on returned entity is only allowed on media resources.", + UriParserSemanticException.MessageKeys.NOT_A_MEDIA_RESOURCE, resource.getSegmentValue()); + } + } + } + private UriResource count(final UriResource previous) throws UriParserException { ParserHelper.requireTokenEnd(tokenizer); requireTyped(previous, "$count"); http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/73f46510/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java index 423c875..db33a6a 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriParserSemanticException.java @@ -66,7 +66,9 @@ public class UriParserSemanticException extends UriParserException { /** parameter: complex parameter value */ COMPLEX_PARAMETER_IN_RESOURCE_PATH, /** parameters: left type, right type */ - TYPES_NOT_COMPATIBLE; + TYPES_NOT_COMPATIBLE, + /** parameter: addressed resource name*/ + NOT_A_MEDIA_RESOURCE; @Override public String getKey() { http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/73f46510/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties index ef5e744..c563a67 100644 --- a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties +++ b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties @@ -72,6 +72,7 @@ UriParserSemanticException.NOT_IMPLEMENTED='%1$s' is not implemented! UriParserSemanticException.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT=Namespace is not allowed for Entity Sets, Singletons, Action Imports and Function Imports; found '%1$s'. UriParserSemanticException.COMPLEX_PARAMETER_IN_RESOURCE_PATH=Complex parameters must not appear in resource path segments; found: '%1$s'. UriParserSemanticException.TYPES_NOT_COMPATIBLE=The types '%1$s' and '%2$s' are not compatible. +UriParserSemanticException.NOT_A_MEDIA_RESOURCE=The resource '%1$s' is not a media resource. $value can only be applied on media resources. UriValidationException.UNSUPPORTED_QUERY_OPTION=The query option '%1$s' is not supported. UriValidationException.UNSUPPORTED_URI_KIND=The URI kind '%1$s' is not supported. http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/73f46510/lib/server-test/src/test/java/org/apache/olingo/server/core/ODataHandlerTest.java ---------------------------------------------------------------------- diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/ODataHandlerTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/ODataHandlerTest.java index d4b87d4..a228e40 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/ODataHandlerTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/ODataHandlerTest.java @@ -318,7 +318,7 @@ public class ODataHandlerTest { dispatch(HttpMethod.GET, "FICRTString()", primitiveProcessor); verify(primitiveProcessor).readPrimitive( any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class)); - + // FINRTInt16 is not composable so /$value is not allowed final String valueUri = "FINRTInt16()/$value"; final PrimitiveValueProcessor primitiveValueProcessor = mock(PrimitiveValueProcessor.class); @@ -327,7 +327,7 @@ public class ODataHandlerTest { dispatchMethodWithError(HttpMethod.PATCH, valueUri, primitiveValueProcessor, HttpStatusCode.BAD_REQUEST); dispatchMethodWithError(HttpMethod.PUT, valueUri, primitiveValueProcessor, HttpStatusCode.BAD_REQUEST); dispatchMethodWithError(HttpMethod.DELETE, valueUri, primitiveValueProcessor, HttpStatusCode.BAD_REQUEST); - + final String primitiveCollectionUri = "FICRTCollString()"; PrimitiveCollectionProcessor primitiveCollectionProcessor = mock(PrimitiveCollectionProcessor.class); dispatch(HttpMethod.GET, primitiveCollectionUri, primitiveCollectionProcessor); @@ -495,6 +495,52 @@ public class ODataHandlerTest { dispatchMethodNotAllowed(HttpMethod.POST, uri, processor); dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor); } + + @Test + public void dispatchValueOnNoMedia() throws Exception { + final String uri = "ESAllPrim(1)/$value"; + final MediaEntityProcessor processor = mock(MediaEntityProcessor.class); + + dispatch(HttpMethod.GET, uri, processor); + verifyZeroInteractions(processor); + + dispatch(HttpMethod.POST, uri, processor); + verifyZeroInteractions(processor); + + dispatch(HttpMethod.PUT, uri, processor); + verifyZeroInteractions(processor); + + dispatch(HttpMethod.DELETE, uri, processor); + verifyZeroInteractions(processor); + } + + @Test + public void dispatchMediaWithNavigation() throws Exception { + /* + * In Java we decided that any kind of navigation will be accepted. This means that a $value on a media resource + * must be dispatched as well + */ + final String uri = "ESKeyNav(1)/NavPropertyETMediaOne/$value"; + final MediaEntityProcessor processor = mock(MediaEntityProcessor.class); + + dispatch(HttpMethod.GET, uri, processor); + verify(processor).readMediaEntity( + any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class)); + + dispatchMethodNotAllowed(HttpMethod.POST, "ESKeyNav(1)/NavPropertyETMediaOne", processor); + + dispatchMethodNotAllowed(HttpMethod.POST, "ESKeyNav(1)/NavPropertyETMediaOne/$value", processor); + + dispatch(HttpMethod.PUT, uri, processor); + verify(processor).updateMediaEntity(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), + any(ContentType.class), any(ContentType.class)); + + dispatch(HttpMethod.DELETE, uri, processor); + verify(processor).deleteMediaEntity(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class)); + + dispatchMethodNotAllowed(HttpMethod.POST, uri, processor); + dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor); + } @Test public void dispatchMediaDeleteIndirect() throws Exception { @@ -758,8 +804,8 @@ public class ODataHandlerTest { assertEquals(HttpStatusCode.METHOD_NOT_ALLOWED.getStatusCode(), response.getStatusCode()); assertNotNull(response.getContent()); } - - private void dispatchMethodWithError(final HttpMethod method, final String path, final Processor processor, + + private void dispatchMethodWithError(final HttpMethod method, final String path, final Processor processor, final HttpStatusCode statusCode) { final ODataResponse response = dispatch(method, path, processor); assertEquals(statusCode.getStatusCode(), response.getStatusCode()); http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/73f46510/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/TestFullResourcePath.java ---------------------------------------------------------------------- diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/TestFullResourcePath.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/TestFullResourcePath.java index fc9a0b8..32c56b4 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/TestFullResourcePath.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/parser/TestFullResourcePath.java @@ -58,6 +58,20 @@ public class TestFullResourcePath { private final FilterValidator testFilter = new FilterValidator().setEdm(edm); @Test + public void valueOnNonMediaEntity() throws Exception { + testUri.runEx("ESAllPrim/$value").isExSemantic(UriParserSemanticException.MessageKeys.ONLY_FOR_TYPED_PARTS); + testUri.runEx("ESAllPrim(1)/NavPropertyETTwoPrimMany/$value").isExSemantic( + UriParserSemanticException.MessageKeys.ONLY_FOR_TYPED_PARTS); + testUri.runEx("FICRTCollESMedia()/$value") + .isExSemantic(UriParserSemanticException.MessageKeys.ONLY_FOR_TYPED_PARTS); + + testUri.runEx("ESAllPrim(1)/$value").isExSemantic(UriParserSemanticException.MessageKeys.NOT_A_MEDIA_RESOURCE); + testUri.runEx("ESAllPrim(1)/NavPropertyETTwoPrimOne/$value").isExSemantic( + UriParserSemanticException.MessageKeys.NOT_A_MEDIA_RESOURCE); + testUri.runEx("FICRTETKeyNav()/$value").isExSemantic(UriParserSemanticException.MessageKeys.NOT_A_MEDIA_RESOURCE); + } + + @Test public void enumAndTypeDefAsKey() throws Exception { testUri .run("ESMixEnumDefCollComp(PropertyEnumString=olingo.odata.test1.ENString'String1',PropertyDefString='abc')") @@ -3205,15 +3219,6 @@ public class TestFullResourcePath { .isType(EntityTypeProvider.nameETTwoKeyNav) .isTypeFilterOnEntry(EntityTypeProvider.nameETBaseTwoKeyNav) .n().isRef(); - - testUri.run("ESTwoKeyNav(PropertyInt16=1,PropertyString='2')/olingo.odata.test1.ETBaseTwoKeyNav/$value") - .goPath().first() - .isEntitySet("ESTwoKeyNav") - .isKeyPredicate(0, "PropertyInt16", "1") - .isKeyPredicate(1, "PropertyString", "'2'") - .isType(EntityTypeProvider.nameETTwoKeyNav) - .isTypeFilterOnEntry(EntityTypeProvider.nameETBaseTwoKeyNav) - .n().isValue(); } @Test @@ -3768,7 +3773,7 @@ public class TestFullResourcePath { public void filterFunctions() throws Exception { testFilter.runOnETAllPrim( "olingo.odata.test1.UFCRTETTwoKeyNavParamCTTwoPrim(ParameterCTTwoPrim=@ParamAlias) eq null" - + "&@ParamAlias={}") + + "&@ParamAlias={}") .is("<<UFCRTETTwoKeyNavParamCTTwoPrim> eq <null>>") .left().goPath() .first() @@ -4687,7 +4692,7 @@ public class TestFullResourcePath { testFilter.runOnETKeyNav("NavPropertyETTwoKeyNavMany/any(d:d/PropertyInt16 eq 1 or " + "d/CollPropertyString/any(e:e eq 'SomeString'))") - .is("<NavPropertyETTwoKeyNavMany/<ANY;<<<d/PropertyInt16> eq <1>>" + .is("<NavPropertyETTwoKeyNavMany/<ANY;<<<d/PropertyInt16> eq <1>>" + " or <d/CollPropertyString/<ANY;<<e> eq <'SomeString'>>>>>>>") .root().goPath() .first().isNavProperty("NavPropertyETTwoKeyNavMany", EntityTypeProvider.nameETTwoKeyNav, true) @@ -4708,7 +4713,7 @@ public class TestFullResourcePath { .isType(EntityTypeProvider.nameETTwoKeyNav, false) .n().isUriPathInfoKind(UriResourceKind.primitiveProperty) .isPrimitiveProperty("CollPropertyString", PropertyProvider.nameString, true) - .at(2).isUriPathInfoKind(UriResourceKind.lambdaAny) + .at(2).isUriPathInfoKind(UriResourceKind.lambdaAny) .goLambdaExpression() .root().left().goPath() .first().isUriPathInfoKind(UriResourceKind.lambdaVariable) @@ -4743,7 +4748,7 @@ public class TestFullResourcePath { .isType(PropertyProvider.nameString, false); testFilter.runOnETKeyNav("NavPropertyETTwoKeyNavMany/any(d:d/PropertyString eq 'SomeString' and " - + "d/CollPropertyString/any(e:e eq d/PropertyString))") + + "d/CollPropertyString/any(e:e eq d/PropertyString))") .is("<NavPropertyETTwoKeyNavMany/<ANY;<<<d/PropertyString> eq <'SomeString'>> and " + "<d/CollPropertyString/<ANY;<<e> eq <d/PropertyString>>>>>>>") .root().goPath() @@ -5415,82 +5420,82 @@ public class TestFullResourcePath { */ @Test public void searchQueryPhraseAbnfTestcases() throws Exception { - // <TestCase Name="5.1.7 Search - simple phrase" Rule="queryOptions"> + // <TestCase Name="5.1.7 Search - simple phrase" Rule="queryOptions"> testUri.run("ESTwoKeyNav", "$search=\"blue%20green\""); - // <TestCase Name="5.1.7 Search - simple phrase" Rule="queryOptions"> + // <TestCase Name="5.1.7 Search - simple phrase" Rule="queryOptions"> testUri.run("ESTwoKeyNav", "$search=\"blue%20green%22"); - // <TestCase Name="5.1.7 Search - phrase with escaped double-quote" Rule="queryOptions"> - // <Input>$search="blue\"green"</Input> + // <TestCase Name="5.1.7 Search - phrase with escaped double-quote" Rule="queryOptions"> + // <Input>$search="blue\"green"</Input> testUri.run("ESTwoKeyNav", "$search=\"blue\\\"green\""); - // <TestCase Name="5.1.7 Search - phrase with escaped backslash" Rule="queryOptions"> - // <Input>$search="blue\\green"</Input> + // <TestCase Name="5.1.7 Search - phrase with escaped backslash" Rule="queryOptions"> + // <Input>$search="blue\\green"</Input> testUri.run("ESTwoKeyNav", "$search=\"blue\\\\green\""); - // <TestCase Name="5.1.7 Search - phrase with unescaped double-quote" Rule="queryOptions" FailAt="14"> + // <TestCase Name="5.1.7 Search - phrase with unescaped double-quote" Rule="queryOptions" FailAt="14"> testUri.runEx("ESTwoKeyNav", "$search=\"blue\"green\"") - .isExceptionMessage(SearchParserException.MessageKeys.TOKENIZER_EXCEPTION); - // <TestCase Name="5.1.7 Search - phrase with unescaped double-quote" Rule="queryOptions" FailAt="16"> + .isExceptionMessage(SearchParserException.MessageKeys.TOKENIZER_EXCEPTION); + // <TestCase Name="5.1.7 Search - phrase with unescaped double-quote" Rule="queryOptions" FailAt="16"> testUri.runEx("ESTwoKeyNav", "$search=\"blue%22green\"") - .isExceptionMessage(SearchParserException.MessageKeys.TOKENIZER_EXCEPTION); + .isExceptionMessage(SearchParserException.MessageKeys.TOKENIZER_EXCEPTION); - // <TestCase Name="5.1.7 Search - implicit AND" Rule="queryOptions"> - // <Input>$search=blue green</Input> - // SearchassertQuery("\"blue%20green\"").resultsIn(); + // <TestCase Name="5.1.7 Search - implicit AND" Rule="queryOptions"> + // <Input>$search=blue green</Input> + // SearchassertQuery("\"blue%20green\"").resultsIn(); testUri.run("ESTwoKeyNav", "$search=blue green"); - // <TestCase Name="5.1.7 Search - implicit AND, encoced" Rule="queryOptions"> - // SearchassertQuery("blue%20green").resultsIn(); + // <TestCase Name="5.1.7 Search - implicit AND, encoced" Rule="queryOptions"> + // SearchassertQuery("blue%20green").resultsIn(); testUri.run("ESTwoKeyNav", "$search=blue%20green"); - // <TestCase Name="5.1.7 Search - AND" Rule="queryOptions"> - // <Input>$search=blue AND green</Input> + // <TestCase Name="5.1.7 Search - AND" Rule="queryOptions"> + // <Input>$search=blue AND green</Input> testUri.run("ESTwoKeyNav", "$search=blue AND green"); - // <TestCase Name="5.1.7 Search - OR" Rule="queryOptions"> - // <Input>$search=blue OR green</Input> + // <TestCase Name="5.1.7 Search - OR" Rule="queryOptions"> + // <Input>$search=blue OR green</Input> testUri.run("ESTwoKeyNav", "$search=blue OR green"); - // <TestCase Name="5.1.7 Search - NOT" Rule="queryOptions"> - // <Input>$search=blue NOT green</Input> + // <TestCase Name="5.1.7 Search - NOT" Rule="queryOptions"> + // <Input>$search=blue NOT green</Input> testUri.run("ESTwoKeyNav", "$search=blue NOT green"); - // <TestCase Name="5.1.7 Search - only NOT" Rule="queryOptions"> - // <Input>$search=NOT blue</Input> + // <TestCase Name="5.1.7 Search - only NOT" Rule="queryOptions"> + // <Input>$search=NOT blue</Input> testUri.run("ESTwoKeyNav", "$search=NOT blue"); - // <TestCase Name="5.1.7 Search - multiple" Rule="queryOptions"> - // <Input>$search=foo AND bar OR foo AND baz OR that AND bar OR that AND baz</Input> + // <TestCase Name="5.1.7 Search - multiple" Rule="queryOptions"> + // <Input>$search=foo AND bar OR foo AND baz OR that AND bar OR that AND baz</Input> testUri.run("ESTwoKeyNav", "$search=foo AND bar OR foo AND baz OR that AND bar OR that AND baz"); - // <TestCase Name="5.1.7 Search - multiple" Rule="queryOptions"> - // <Input>$search=(foo OR that) AND (bar OR baz)</Input> + // <TestCase Name="5.1.7 Search - multiple" Rule="queryOptions"> + // <Input>$search=(foo OR that) AND (bar OR baz)</Input> testUri.run("ESTwoKeyNav", "$search=(foo OR that) AND (bar OR baz)"); - // <TestCase Name="5.1.7 Search - grouping" Rule="queryOptions"> - // <Input>$search=foo AND (bar OR baz)</Input> + // <TestCase Name="5.1.7 Search - grouping" Rule="queryOptions"> + // <Input>$search=foo AND (bar OR baz)</Input> testUri.run("ESTwoKeyNav", "$search=foo AND (bar OR baz)"); - // <TestCase Name="5.1.7 Search - grouping" Rule="queryOptions"> - // <Input>$search=(foo AND bar) OR baz</Input> + // <TestCase Name="5.1.7 Search - grouping" Rule="queryOptions"> + // <Input>$search=(foo AND bar) OR baz</Input> testUri.run("ESTwoKeyNav", "$search=(foo AND bar) OR baz"); - // <TestCase Name="5.1.7 Search - grouping" Rule="queryOptions"> - // <Input>$search=(NOT foo) OR baz</Input> + // <TestCase Name="5.1.7 Search - grouping" Rule="queryOptions"> + // <Input>$search=(NOT foo) OR baz</Input> testUri.run("ESTwoKeyNav", "$search=(NOT foo) OR baz"); - // <TestCase Name="5.1.7 Search - grouping" Rule="queryOptions"> - // <Input>$search=(NOT foo)</Input> + // <TestCase Name="5.1.7 Search - grouping" Rule="queryOptions"> + // <Input>$search=(NOT foo)</Input> testUri.run("ESTwoKeyNav", "$search=(NOT foo)"); - // <TestCase Name="5.1.7 Search - on entity set" Rule="odataUri"> - // <Input>http://serviceRoot/Products?$search=blue</Input> + // <TestCase Name="5.1.7 Search - on entity set" Rule="odataUri"> + // <Input>http://serviceRoot/Products?$search=blue</Input> testUri.run("ESTwoKeyNav", "$search=blue"); - // <TestCase Name="5.1.7 Search - on entity container" Rule="odataUri"> - // <Input>http://serviceRoot/Model.Container/$all?$search=blue</Input> + // <TestCase Name="5.1.7 Search - on entity container" Rule="odataUri"> + // <Input>http://serviceRoot/Model.Container/$all?$search=blue</Input> testUri.run("$all", "$search=blue"); - // <TestCase Name="5.1.7 Search - on service" Rule="odataUri"> - // <Input>http://serviceRoot/$all?$search=blue</Input> + // <TestCase Name="5.1.7 Search - on service" Rule="odataUri"> + // <Input>http://serviceRoot/$all?$search=blue</Input> testUri.run("$all", "$search=blue"); } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/73f46510/lib/server-test/src/test/java/org/apache/olingo/server/core/uri/testutil/TestUriValidator.java ---------------------------------------------------------------------- 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 fbce5c9..fa1e440 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 @@ -90,6 +90,7 @@ public class TestUriValidator implements TestValidator { exception = e; } catch (UriValidationException e) { exception = e; + e.printStackTrace(); } return this; }
