diff --git a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/NavigationITCase.java b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/NavigationITCase.java index e8147a4ed..40c1de433 100644 --- a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/NavigationITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/NavigationITCase.java @@ -20,6 +20,7 @@ package org.apache.olingo.fit.tecsvc.client; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import org.apache.olingo.client.api.ODataClient; import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse; @@ -31,12 +32,48 @@ import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.commons.api.http.HttpStatusCode; import org.apache.olingo.fit.AbstractBaseTestITCase; import org.apache.olingo.fit.tecsvc.TecSvcConst; +import org.apache.olingo.fit.util.StringHelper; import org.junit.Test; +import java.io.InputStream; + public final class NavigationITCase extends AbstractBaseTestITCase { private final ODataClient client = getClient(); + @Test + public void navigationToEntityWithRelativeContextUrl() throws Exception { + // zero navigation + final InputStream zeroLevelResponse = client.getRetrieveRequestFactory().getEntityRequest( + client.newURIBuilder(TecSvcConst.BASE_URI) + .appendEntitySetSegment("ESAllPrim"). + appendKeySegment(32767).build()).rawExecute(); + + String zeroLevelResponseBody = StringHelper.asString(zeroLevelResponse); + assertTrue(zeroLevelResponseBody.contains("\"@odata.context\":\"$metadata#ESAllPrim/$entity\"")); + + // one navigation + final InputStream oneLevelResponse = client.getRetrieveRequestFactory().getEntityRequest( + client.newURIBuilder(TecSvcConst.BASE_URI) + .appendEntitySetSegment("ESAllPrim").appendKeySegment(32767) + .appendNavigationSegment("NavPropertyETTwoPrimOne").build()) + .rawExecute(); + + String oneLevelResponseBody = StringHelper.asString(oneLevelResponse); + assertTrue(oneLevelResponseBody.contains("\"@odata.context\":\"../$metadata#ESTwoPrim/$entity\"")); + + // two navigation + final InputStream twoLevelResponse = client.getRetrieveRequestFactory().getEntityRequest( + client.newURIBuilder(TecSvcConst.BASE_URI) + .appendEntitySetSegment("ESTwoPrim").appendKeySegment(32767) + .appendNavigationSegment("NavPropertyETAllPrimOne") + .appendNavigationSegment("NavPropertyETTwoPrimMany").appendKeySegment(-365).build()) + .rawExecute(); + + String twoLevelResponseBody = StringHelper.asString(twoLevelResponse); + assertTrue(twoLevelResponseBody.contains("\"@odata.context\":\"../../$metadata#ESTwoPrim/$entity\"")); + } + @Test public void oneLevelToEntity() throws Exception { final ODataRetrieveResponse response = diff --git a/fit/src/test/java/org/apache/olingo/fit/util/StringHelper.java b/fit/src/test/java/org/apache/olingo/fit/util/StringHelper.java new file mode 100644 index 000000000..8b2e9a1b7 --- /dev/null +++ b/fit/src/test/java/org/apache/olingo/fit/util/StringHelper.java @@ -0,0 +1,214 @@ +/******************************************************************************* + * 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.fit.util; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.Random; + +/** + * + */ +public class StringHelper { + + public static final String DEFAULT_ENCODING = "UTF-8"; + + public static class Stream { + private final byte[] data; + + private Stream(final byte[] data) { + this.data = data; + } + + public Stream(final String content, final String charset) throws UnsupportedEncodingException { + this(content.getBytes(charset)); + } + + public InputStream asStream() { + return new ByteArrayInputStream(data); + } + + public byte[] asArray() { + return data; + } + + public int byteLength() { + if(data == null) { + return -1; + } + return data.length; + } + + public String asString() { + return asString(DEFAULT_ENCODING); + } + + public String asString(final String charsetName) { + return new String(data, Charset.forName(charsetName)); + } + + public Stream print(final OutputStream out) throws IOException { + out.write(data); + return this; + } + + public Stream print() throws IOException { + return print(System.out); + } + + public String asStringWithLineSeparation(String separator) throws IOException { + BufferedReader br = new BufferedReader(new StringReader(asString())); + StringBuilder sb = new StringBuilder(br.readLine()); + String line = br.readLine(); + while (line != null) { + sb.append(separator).append(line); + line = br.readLine(); + } + return sb.toString(); + } + + public InputStream asStreamWithLineSeparation(String separator) throws IOException { + String asString = asStringWithLineSeparation(separator); + return new ByteArrayInputStream(asString.getBytes(DEFAULT_ENCODING)); + } + + /** + * Number of lines separated by line breaks (CRLF). + * A content string like text\r\nmoreText will result in + * a line count of 2. + * + * @return lines count + */ + public int linesCount() { + return StringHelper.countLines(asString(), "\r\n"); + } + } + + public static Stream toStream(final InputStream stream) throws IOException { + byte[] result = new byte[0]; + byte[] tmp = new byte[8192]; + int readCount = stream.read(tmp); + while (readCount >= 0) { + byte[] innerTmp = new byte[result.length + readCount]; + System.arraycopy(result, 0, innerTmp, 0, result.length); + System.arraycopy(tmp, 0, innerTmp, result.length, readCount); + result = innerTmp; + readCount = stream.read(tmp); + } + stream.close(); + return new Stream(result); + } + + public static Stream toStream(final String content) { + return toStream(content, DEFAULT_ENCODING); + } + + public static Stream toStream(final String content, final String charset) { + try { + return new Stream(content, charset); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 should be supported on each system."); + } + } + + /** + * Read the input stream into an string with default encoding (see StringHelper.DEFAULT_ENCODING). + * + * @param in stream which is read + * @return content of stream as string + * @throws IOException + */ + public static String asString(final InputStream in) throws IOException { + return toStream(in).asString(); + } + + public static int countLines(final String content, final String lineBreak) { + if (content == null) { + return -1; + } + + int lastPos = content.indexOf(lineBreak); + int count = 1; + + while (lastPos >= 0) { + lastPos = content.indexOf(lineBreak, lastPos + 1); + count++; + } + return count; + } + + /** + * Encapsulate given content in an {@link InputStream} with charset UTF-8. + * + * @param content to encapsulate content + * @return content as stream + */ + public static InputStream encapsulate(final String content) { + try { + return encapsulate(content, DEFAULT_ENCODING); + } catch (UnsupportedEncodingException e) { + // we know that UTF-8 is supported + throw new RuntimeException("UTF-8 MUST be supported.", e); + } + } + + /** + * Encapsulate given content in an {@link InputStream} with given charset. + * + * @param content to encapsulate content + * @param charset to be used charset + * @return content as stream + * @throws UnsupportedEncodingException if charset is not supported + */ + public static InputStream encapsulate(final String content, final String charset) + throws UnsupportedEncodingException { + return new ByteArrayInputStream(content.getBytes(charset)); + } + + /** + * Generate a string with given length containing random upper case characters ([A-Z]). + * + * @param len length of to generated string + * @return random upper case characters ([A-Z]). + */ + public static InputStream generateDataStream(final int len) { + return encapsulate(generateData(len)); + } + + /** + * Generates a string with given length containing random upper case characters ([A-Z]). + * @param len length of the generated string + * @return random upper case characters ([A-Z]) + */ + public static String generateData(final int len) { + Random random = new Random(); + StringBuilder b = new StringBuilder(len); + for (int j = 0; j < len; j++) { + final char c = (char) ('A' + random.nextInt('Z' - 'A' + 1)); + b.append(c); + } + return b.toString(); + } +} diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/ContextURL.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/ContextURL.java index 3c482c73e..bf31d7135 100644 --- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/ContextURL.java +++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/ContextURL.java @@ -48,6 +48,12 @@ public class ContextURL { private Suffix suffix; + private String odataPath; + + public String getODataPath() { + return odataPath; + } + public enum Suffix { ENTITY("$entity"), REFERENCE("$ref"), @@ -55,7 +61,7 @@ public class ContextURL { private final String representation; - private Suffix(final String representation) { + Suffix(final String representation) { this.representation = representation; } @@ -128,7 +134,12 @@ public class ContextURL { public static final class Builder { - private ContextURL contextURL = new ContextURL(); + private final ContextURL contextURL = new ContextURL(); + + public Builder oDataPath(String oDataPath) { + contextURL.odataPath = oDataPath; + return this; + } public Builder serviceRoot(final URI serviceRoot) { contextURL.serviceRoot = serviceRoot; diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/utils/ContextURLBuilder.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/utils/ContextURLBuilder.java index e79a295a2..3cc774488 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/utils/ContextURLBuilder.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/utils/ContextURLBuilder.java @@ -35,7 +35,16 @@ public final class ContextURLBuilder { StringBuilder result = new StringBuilder(); if (contextURL.getServiceRoot() != null) { result.append(contextURL.getServiceRoot()); + } else if(contextURL.getODataPath() != null) { + String oDataPath = contextURL.getODataPath(); + char[] chars = oDataPath.toCharArray(); + for (int i = 1; i < chars.length-1; i++) { + if(chars[i] == '/' && chars[i-1] != '/') { + result.append("../"); + } + } } + result.append(Constants.METADATA); if (contextURL.getEntitySetOrSingletonOrType() != null) { result.append('#'); diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java index cba853d28..a47f36744 100644 --- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java +++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java @@ -187,7 +187,7 @@ public class TechnicalEntityProcessor extends TechnicalProcessor + odata.createUriHelper().buildCanonicalURL(edmEntitySet, entity); final Return returnPreference = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getReturn(); if (returnPreference == null || returnPreference == Return.REPRESENTATION) { - response.setContent(serializeEntity(entity, edmEntitySet, edmEntityType, responseFormat, expand, null) + response.setContent(serializeEntity(request, entity, edmEntitySet, edmEntityType, responseFormat, expand, null) .getContent()); response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); response.setStatusCode(HttpStatusCode.CREATED.getStatusCode()); @@ -243,7 +243,7 @@ public class TechnicalEntityProcessor extends TechnicalProcessor final Return returnPreference = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getReturn(); if (returnPreference == null || returnPreference == Return.REPRESENTATION) { response.setStatusCode(HttpStatusCode.OK.getStatusCode()); - response.setContent(serializeEntity(entity, edmEntitySet, edmEntityType, responseFormat, null, null) + response.setContent(serializeEntity(request, entity, edmEntitySet, edmEntityType, responseFormat) .getContent()); response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); } else { @@ -271,12 +271,12 @@ public class TechnicalEntityProcessor extends TechnicalProcessor request.getHeaders(HttpHeader.IF_NONE_MATCH)); checkRequestFormat(requestFormat); dataProvider.setMedia(entity, odata.createFixedFormatDeserializer().binary(request.getBody()), - requestFormat.toContentTypeString()); + requestFormat.toContentTypeString()); final Return returnPreference = odata.createPreferences(request.getHeaders(HttpHeader.PREFER)).getReturn(); if (returnPreference == null || returnPreference == Return.REPRESENTATION) { - response.setContent(serializeEntity(entity, edmEntitySet, edmEntityType, responseFormat, null, null) - .getContent()); + response.setContent(serializeEntity(request, entity, edmEntitySet, edmEntityType, responseFormat) + .getContent()); response.setStatusCode(HttpStatusCode.OK.getStatusCode()); response.setHeader(HttpHeader.CONTENT_TYPE, responseFormat.toContentTypeString()); } else { @@ -385,16 +385,17 @@ public class TechnicalEntityProcessor extends TechnicalProcessor final Entity entity = readEntity(uriInfo, true); dataProvider.deleteReference(entity, - lastNavigation.getProperty(), - (uriInfo.getIdOption() != null) ? uriInfo.getIdOption().getValue() : null, - request.getRawBaseUri()); + lastNavigation.getProperty(), + (uriInfo.getIdOption() != null) ? uriInfo.getIdOption().getValue() : null, + request.getRawBaseUri()); response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode()); } @Override - public void readReferenceCollection(final ODataRequest request, final ODataResponse response, final UriInfo uriInfo, - final ContentType requestedContentType) throws ODataApplicationException, ODataLibraryException { + public void readReferenceCollection(final ODataRequest request, final ODataResponse response, + final UriInfo uriInfo, final ContentType requestedContentType) + throws ODataApplicationException, ODataLibraryException { readEntityCollection(request, response, uriInfo, requestedContentType, true); } @@ -440,7 +441,7 @@ public class TechnicalEntityProcessor extends TechnicalProcessor final SerializerResult serializerResult = isReference ? serializeReference(entity, edmEntitySet, requestedFormat) : - serializeEntity(entitySerialization, edmEntitySet, edmEntityType, requestedFormat, expand, select); + serializeEntity(request, entitySerialization, edmEntitySet, edmEntityType, requestedFormat, expand, select); if (entity.getETag() != null) { response.setHeader(HttpHeader.ETAG, entity.getETag()); @@ -516,7 +517,7 @@ public class TechnicalEntityProcessor extends TechnicalProcessor // Serialize final SerializerResult serializerResult = (isReference) ? serializeReferenceCollection(entitySetSerialization, edmEntitySet, requestedContentType, countOption) : - serializeEntityCollection(entitySetSerialization, edmEntitySet, edmEntityType, requestedContentType, + serializeEntityCollection(request, entitySetSerialization, edmEntitySet, edmEntityType, requestedContentType, expand, select, countOption); response.setContent(serializerResult.getContent()); @@ -528,21 +529,23 @@ public class TechnicalEntityProcessor extends TechnicalProcessor } } - private SerializerResult serializeEntityCollection(final EntityCollection entityCollection, + private SerializerResult serializeEntityCollection(final ODataRequest request, final EntityCollection + entityCollection, final EdmEntitySet edmEntitySet, final EdmEntityType edmEntityType, final ContentType requestedFormat, final ExpandOption expand, final SelectOption select, final CountOption countOption) throws ODataLibraryException { - + return odata.createSerializer(requestedFormat).entityCollection( - serviceMetadata, - edmEntityType, - entityCollection, - EntityCollectionSerializerOptions.with() - .contextURL(isODataMetadataNone(requestedFormat) ? null : - getContextUrl(edmEntitySet, edmEntityType, false, expand, select)) - .count(countOption) - .expand(expand).select(select) - .build()); + serviceMetadata, + edmEntityType, + entityCollection, + EntityCollectionSerializerOptions.with() + .contextURL(isODataMetadataNone(requestedFormat) ? null : + getContextUrl(request.getRawODataPath(), edmEntitySet, edmEntityType, false, expand, + select)) + .count(countOption) + .expand(expand).select(select) + .build()); } private SerializerResult serializeReferenceCollection(final EntityCollection entityCollection, @@ -550,36 +553,50 @@ public class TechnicalEntityProcessor extends TechnicalProcessor throws ODataLibraryException { return odata.createSerializer(requestedFormat) - .referenceCollection(serviceMetadata, edmEntitySet, entityCollection,ReferenceCollectionSerializerOptions.with() - .contextURL(ContextURL.with().asCollection().suffix(Suffix.REFERENCE).build()) - .count(countOption).build()); + .referenceCollection(serviceMetadata, edmEntitySet, entityCollection, + ReferenceCollectionSerializerOptions.with() + .contextURL(ContextURL.with().asCollection().suffix(Suffix.REFERENCE).build()) + .count(countOption).build()); } private SerializerResult serializeReference(final Entity entity, final EdmEntitySet edmEntitySet, final ContentType requestedFormat) throws ODataLibraryException { return odata.createSerializer(requestedFormat) .reference(serviceMetadata, edmEntitySet, entity, ReferenceSerializerOptions.with() - .contextURL(ContextURL.with().suffix(Suffix.REFERENCE).build()).build()); + .contextURL(ContextURL.with().suffix(Suffix.REFERENCE).build()).build()); } - private SerializerResult serializeEntity(final Entity entity, - final EdmEntitySet edmEntitySet, final EdmEntityType edmEntityType, final ContentType requestedFormat, - final ExpandOption expand, final SelectOption select) throws ODataLibraryException { - return odata.createSerializer(requestedFormat).entity( - serviceMetadata, - edmEntityType, - entity, - EntitySerializerOptions.with() - .contextURL(isODataMetadataNone(requestedFormat) ? null : - getContextUrl(edmEntitySet, edmEntityType, true, expand, select)) - .expand(expand).select(select) - .build()); + private SerializerResult serializeEntity(final ODataRequest request, final Entity entity, + final EdmEntitySet edmEntitySet, final EdmEntityType edmEntityType, + final ContentType requestedFormat) throws ODataLibraryException { + return serializeEntity(request, entity, edmEntitySet, edmEntityType, requestedFormat, null, null); } - private ContextURL getContextUrl(final EdmEntitySet entitySet, final EdmEntityType entityType, - final boolean isSingleEntity, final ExpandOption expand, final SelectOption select) throws ODataLibraryException { - Builder builder = ContextURL.with(); + private SerializerResult serializeEntity(final ODataRequest request, final Entity entity, + final EdmEntitySet edmEntitySet, final EdmEntityType edmEntityType, + final ContentType requestedFormat, + final ExpandOption expand, final SelectOption select) + throws ODataLibraryException { + + ContextURL contextUrl = isODataMetadataNone(requestedFormat) ? null : + getContextUrl(request.getRawODataPath(), edmEntitySet, edmEntityType, true, expand, null); + return odata.createSerializer(requestedFormat).entity( + serviceMetadata, + edmEntityType, + entity, + EntitySerializerOptions.with() + .contextURL(contextUrl) + .expand(expand).select(select) + .build()); + } + + private ContextURL getContextUrl(String rawODataPath, final EdmEntitySet entitySet, final EdmEntityType entityType, + final boolean isSingleEntity, final ExpandOption expand, final SelectOption select) + throws ODataLibraryException { + // + // + Builder builder = ContextURL.with().oDataPath(rawODataPath); builder = entitySet == null ? isSingleEntity ? builder.type(entityType) : builder.asCollection().type(entityType) : builder.entitySet(entitySet); 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 b94f97e95..fe86896f0 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 @@ -95,6 +95,76 @@ public class ContextURLHelperTest { ContextURLBuilder.create(contextURL).toASCIIString()); } + + @Test + public void buildEntity() throws Exception { + final EdmEntitySet entitySet = entityContainer.getEntitySet("ESTwoPrim"); + final ContextURL contextURL = ContextURL.with().entitySet(entitySet).suffix(ContextURL.Suffix.ENTITY).build(); + assertEquals("$metadata#ESTwoPrim/$entity", ContextURLBuilder.create(contextURL).toASCIIString()); + } + + // /odata.svc/ESAllPrim(32767)/NavPropertyETTwoPrimOne/NavPropertyETAllPrimOne/ + // NavPropertyETTwoPrimOne/NavPropertyETAllPrimOne/NavPropertyETTwoPrimOne + // @odata.context: "../../../../../$metadata#ESTwoPrim/$entity" + @Test + public void buildRelativeFiveNavigation() throws Exception { + final EdmEntitySet entitySet = entityContainer.getEntitySet("ESTwoPrim"); + String odataPath = "ESAllPrim(32767)/NavPropertyETTwoPrimOne/NavPropertyETAllPrimOne/" + + "NavPropertyETTwoPrimOne/NavPropertyETAllPrimOne/NavPropertyETTwoPrimOne"; + ContextURL contextURL = ContextURL.with() + .oDataPath("/" + odataPath) + .entitySet(entitySet).suffix(ContextURL.Suffix.ENTITY).build(); + assertEquals("../../../../../$metadata#ESTwoPrim/$entity", ContextURLBuilder.create(contextURL).toASCIIString()); + + // removed leading '/' + contextURL = ContextURL.with() + .oDataPath(odataPath) + .entitySet(entitySet).suffix(ContextURL.Suffix.ENTITY).build(); + assertEquals("../../../../../$metadata#ESTwoPrim/$entity", ContextURLBuilder.create(contextURL).toASCIIString()); + } + + // odata.svc/ESAllPrim(32767)/NavPropertyETTwoPrimOne + // @odata.context: "$metadata#ESTwoPrim/$entity", + @Test + public void buildRelativeNavigation() throws Exception { + final EdmEntitySet entitySet = entityContainer.getEntitySet("ESTwoPrim"); + final String oDataPath = "ESAllPrim(32767)/NavPropertyETTwoPrimOne"; + ContextURL contextURL = ContextURL.with().oDataPath("/" + oDataPath) + .entitySet(entitySet).suffix(ContextURL.Suffix.ENTITY).build(); + assertEquals("../$metadata#ESTwoPrim/$entity", ContextURLBuilder.create(contextURL).toASCIIString()); + + // without leading '/' + contextURL = ContextURL.with().oDataPath(oDataPath) + .entitySet(entitySet).suffix(ContextURL.Suffix.ENTITY).build(); + assertEquals("../$metadata#ESTwoPrim/$entity", ContextURLBuilder.create(contextURL).toASCIIString()); + } + + // /odata.svc/ESAllPrim(32767)/NavPropertyETTwoPrimOne/NavPropertyETAllPrimOne + // @odata.context: "$metadata#ESAllPrim/$entity", + @Test + public void buildRelativeTwoNavigation() throws Exception { + final EdmEntitySet entitySet = entityContainer.getEntitySet("ESAllPrim"); + String oDataPath = "ESAllPrim(32767)/NavPropertyETTwoPrimOne/NavPropertyETAllPrimOne"; + ContextURL contextURL = ContextURL.with() + .oDataPath("/" + oDataPath) + .entitySet(entitySet).suffix(ContextURL.Suffix.ENTITY).build(); + assertEquals("../../$metadata#ESAllPrim/$entity", ContextURLBuilder.create(contextURL).toASCIIString()); + + // without leading '/' + contextURL = ContextURL.with().oDataPath(oDataPath) + .entitySet(entitySet).suffix(ContextURL.Suffix.ENTITY).build(); + assertEquals("../../$metadata#ESAllPrim/$entity", ContextURLBuilder.create(contextURL).toASCIIString()); + + // without unnecessary '///' + contextURL = ContextURL.with().oDataPath("///" + oDataPath) + .entitySet(entitySet).suffix(ContextURL.Suffix.ENTITY).build(); + assertEquals("../../$metadata#ESAllPrim/$entity", ContextURLBuilder.create(contextURL).toASCIIString()); + // without unnecessary '///' between + contextURL = ContextURL.with().oDataPath(oDataPath.replace("/", "///")) + .entitySet(entitySet).suffix(ContextURL.Suffix.ENTITY).build(); + assertEquals("../../$metadata#ESAllPrim/$entity", ContextURLBuilder.create(contextURL).toASCIIString()); + } + @Test public void buildExpandAll() throws Exception { final EdmEntitySet entitySet = entityContainer.getEntitySet("ESTwoPrim");