You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by mi...@apache.org on 2015/07/31 15:57:23 UTC

olingo-odata4 git commit: [OLINGO-743] Add odatapath to ContextURL

Repository: olingo-odata4
Updated Branches:
  refs/heads/master ac6e9e725 -> 5e481b23e


[OLINGO-743] Add odatapath to ContextURL


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/5e481b23
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/5e481b23
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/5e481b23

Branch: refs/heads/master
Commit: 5e481b23ec0bf41fa83160eca4ce370e936fd7c7
Parents: ac6e9e7
Author: Michael Bolz <mi...@sap.com>
Authored: Fri Jul 31 15:51:34 2015 +0200
Committer: Michael Bolz <mi...@sap.com>
Committed: Fri Jul 31 15:51:34 2015 +0200

----------------------------------------------------------------------
 .../fit/tecsvc/client/NavigationITCase.java     |  37 ++++
 .../apache/olingo/fit/util/StringHelper.java    | 214 +++++++++++++++++++
 .../olingo/commons/api/data/ContextURL.java     |  15 +-
 .../serializer/utils/ContextURLBuilder.java     |   9 +
 .../processor/TechnicalEntityProcessor.java     |  99 +++++----
 .../serializer/utils/ContextURLHelperTest.java  |  70 ++++++
 6 files changed, 401 insertions(+), 43 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/5e481b23/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/NavigationITCase.java
----------------------------------------------------------------------
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 e8147a4..40c1de4 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,13 +32,49 @@ 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<ClientEntity> response =
         client.getRetrieveRequestFactory().getEntityRequest(

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/5e481b23/fit/src/test/java/org/apache/olingo/fit/util/StringHelper.java
----------------------------------------------------------------------
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 0000000..8b2e9a1
--- /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 (<code>CRLF</code>).
+     * A content string like <code>text\r\nmoreText</code> will result in
+     * a line count of <code>2</code>.
+     * 
+     * @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 <code>UTF-8</code>.
+   *
+   * @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();
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/5e481b23/lib/commons-api/src/main/java/org/apache/olingo/commons/api/data/ContextURL.java
----------------------------------------------------------------------
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 3c482c7..bf31d71 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;

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/5e481b23/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/utils/ContextURLBuilder.java
----------------------------------------------------------------------
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 e79a295..3cc7744 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('#');

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/5e481b23/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java
----------------------------------------------------------------------
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 cba853d..a47f367 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 {
+  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 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(isODataMetadataNone(requestedFormat) ? null :
-                getContextUrl(edmEntitySet, edmEntityType, true, expand, select))
-            .expand(expand).select(select)
-            .build());
+            serviceMetadata,
+            edmEntityType,
+            entity,
+            EntitySerializerOptions.with()
+                    .contextURL(contextUrl)
+                    .expand(expand).select(select)
+                    .build());
   }
 
-  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 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);

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/5e481b23/lib/server-test/src/test/java/org/apache/olingo/server/core/serializer/utils/ContextURLHelperTest.java
----------------------------------------------------------------------
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 b94f97e..fe86896 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");