You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by il...@apache.org on 2014/04/04 17:25:35 UTC

[4/7] git commit: [OLINGO-175] Enabling fit to respond to action / function invocation (V3)

[OLINGO-175] Enabling fit to respond to action / function invocation (V3)


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

Branch: refs/heads/master
Commit: bb748ec0b78a09d105b9497685756c699372c7d3
Parents: 7dae5ef
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Fri Apr 4 17:20:42 2014 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Fri Apr 4 17:20:42 2014 +0200

----------------------------------------------------------------------
 fit/pom.xml                                     |   4 +
 .../org/apache/olingo/fit/AbstractServices.java | 385 +++++++-----
 .../org/apache/olingo/fit/V3KeyAsSegment.java   | 159 +++++
 .../java/org/apache/olingo/fit/V3Services.java  |  13 +-
 .../java/org/apache/olingo/fit/V4NorthWind.java |  11 +-
 .../org/apache/olingo/fit/V4NorthWindExt.java   |  11 +-
 .../java/org/apache/olingo/fit/V4Services.java  |   9 +-
 .../olingo/fit/utils/AbstractJSONUtilities.java |  25 +-
 .../olingo/fit/utils/AbstractUtilities.java     |  25 +-
 .../olingo/fit/utils/AbstractXMLUtilities.java  |  60 +-
 .../org/apache/olingo/fit/utils/Commons.java    |   5 +-
 ...ionReturnsCollectionOfComplexTypes.full.json |   1 +
 ...rojectionReturnsCollectionOfComplexTypes.xml | 618 +++++++++++++++++++
 .../resources/v3/GetArgumentPlusOne.full.json   |   4 +
 .../main/resources/v3/GetArgumentPlusOne.xml    |  23 +
 .../resources/v3/GetPrimitiveString.full.json   |   1 +
 .../main/resources/v3/GetPrimitiveString.xml    |  23 +
 .../resources/v3/GetSpecificCustomer.full.json  | 137 ++++
 .../main/resources/v3/GetSpecificCustomer.xml   | 200 ++++++
 .../feed.full.json                              |   1 +
 .../feed.xml                                    | 200 ++++++
 .../main/webapp/WEB-INF/applicationContext.xml  |   1 +
 22 files changed, 1711 insertions(+), 205 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/bb748ec0/fit/pom.xml
----------------------------------------------------------------------
diff --git a/fit/pom.xml b/fit/pom.xml
index 6e53597..d4e1e90 100644
--- a/fit/pom.xml
+++ b/fit/pom.xml
@@ -117,6 +117,10 @@
         <inherited>true</inherited>
         <configuration>
           <configuration>
+            <properties>
+              <cargo.jvmargs>-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n
+                -noverify -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -XX:MaxPermSize=256m</cargo.jvmargs>
+            </properties>
             <files>
               <file>
                 <file>${project.build.directory}/classes/esigate.properties</file>

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/bb748ec0/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java
----------------------------------------------------------------------
diff --git a/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java b/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java
index fd64d3d..bcc6150 100644
--- a/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java
+++ b/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java
@@ -18,17 +18,9 @@
  */
 package org.apache.olingo.fit;
 
-import org.apache.olingo.fit.utils.Accept;
-import org.apache.olingo.fit.utils.AbstractXMLUtilities;
-import org.apache.olingo.fit.utils.AbstractJSONUtilities;
-import org.apache.olingo.fit.utils.ODataVersion;
-import org.apache.olingo.fit.utils.FSManager;
-
-import org.apache.olingo.fit.methods.MERGE;
-import org.apache.olingo.fit.methods.PATCH;
-import org.apache.olingo.fit.utils.AbstractUtilities;
-import org.apache.olingo.fit.utils.Commons;
-import org.apache.olingo.fit.utils.LinkInfo;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.InputStream;
 import java.util.AbstractMap;
@@ -38,6 +30,8 @@ import java.util.EnumSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.DefaultValue;
@@ -50,13 +44,25 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.olingo.fit.methods.MERGE;
+import org.apache.olingo.fit.methods.PATCH;
+import org.apache.olingo.fit.utils.AbstractJSONUtilities;
+import org.apache.olingo.fit.utils.AbstractUtilities;
+import org.apache.olingo.fit.utils.AbstractXMLUtilities;
+import org.apache.olingo.fit.utils.Accept;
+import org.apache.olingo.fit.utils.Commons;
 import org.apache.olingo.fit.utils.ConstantKey;
 import org.apache.olingo.fit.utils.Constants;
+import org.apache.olingo.fit.utils.FSManager;
+import org.apache.olingo.fit.utils.LinkInfo;
+import org.apache.olingo.fit.utils.ODataVersion;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -69,13 +75,18 @@ public abstract class AbstractServices {
 
   private static final Set<ODataVersion> INITIALIZED = EnumSet.noneOf(ODataVersion.class);
 
-  protected abstract ODataVersion getVersion();
+  protected final ODataVersion version;
+
   protected final AbstractXMLUtilities xml;
 
   protected final AbstractJSONUtilities json;
 
-  public AbstractServices() throws Exception {
-    if (ODataVersion.v3 == getVersion()) {
+  @Context
+  protected UriInfo uriInfo;
+
+  public AbstractServices(final ODataVersion version) throws Exception {
+    this.version = version;
+    if (ODataVersion.v3 == version) {
       this.xml = new org.apache.olingo.fit.utils.v3.XMLUtilities();
       this.json = new org.apache.olingo.fit.utils.v3.JSONUtilities();
     } else {
@@ -83,9 +94,9 @@ public abstract class AbstractServices {
       this.json = new org.apache.olingo.fit.utils.v4.JSONUtilities();
     }
 
-    if (!INITIALIZED.contains(getVersion())) {
+    if (!INITIALIZED.contains(version)) {
       xml.retrieveLinkInfoFromMetadata();
-      INITIALIZED.add(getVersion());
+      INITIALIZED.add(version);
     }
   }
 
@@ -98,14 +109,14 @@ public abstract class AbstractServices {
   @GET
   public Response getSevices(@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept) {
     try {
-      final Accept acceptType = Accept.parse(accept, getVersion());
+      final Accept acceptType = Accept.parse(accept, version);
 
       if (acceptType == Accept.ATOM) {
         throw new UnsupportedMediaTypeException("Unsupported media type");
       }
 
       return xml.createResponse(
-              FSManager.instance(getVersion()).readFile(Constants.get(getVersion(), ConstantKey.SERVICES), acceptType),
+              FSManager.instance(version).readFile(Constants.get(version, ConstantKey.SERVICES), acceptType),
               null, acceptType);
     } catch (Exception e) {
       return xml.createFaultResponse(accept, e);
@@ -121,22 +132,16 @@ public abstract class AbstractServices {
   @Path("/$metadata")
   @Produces("application/xml")
   public Response getMetadata() {
-    return getMetadata(Constants.get(getVersion(), ConstantKey.METADATA));
+    return getMetadata(Constants.get(version, ConstantKey.METADATA));
   }
 
   protected Response getMetadata(final String filename) {
     try {
-      return xml.createResponse(FSManager.instance(getVersion()).readFile(filename, Accept.XML), null, Accept.XML);
+      return xml.createResponse(FSManager.instance(version).readFile(filename, Accept.XML), null, Accept.XML);
     } catch (Exception e) {
-      return xml.createFaultResponse(Accept.XML.toString(getVersion()), e);
+      return xml.createFaultResponse(Accept.XML.toString(version), e);
     }
   }
-//
-//  @GET
-//  @Path("/$entity")
-//  public Response getEntityReference(@QueryParam("$id") String id){
-//    return null;
-//  }
 
   /**
    * Retrieve entity reference sample.
@@ -163,7 +168,7 @@ public abstract class AbstractServices {
       final String filename = Base64.encodeBase64String(path.getBytes("UTF-8"));
 
       return utils.getValue().createResponse(
-              FSManager.instance(getVersion()).readFile(Constants.get(getVersion(), ConstantKey.REF)
+              FSManager.instance(version).readFile(Constants.get(version, ConstantKey.REF)
                       + File.separatorChar + filename, utils.getKey()),
               null,
               utils.getKey());
@@ -174,21 +179,6 @@ public abstract class AbstractServices {
   }
 
   @MERGE
-  @Path("/{entitySetName}/{entityId}")
-  @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
-  @Consumes({MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
-  public Response mergeEntityKeyAsSegment(
-          @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
-          @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
-          @HeaderParam("If-Match") @DefaultValue(StringUtils.EMPTY) String ifMatch,
-          @PathParam("entitySetName") String entitySetName,
-          @PathParam("entityId") String entityId,
-          final String changes) {
-
-    return patchEntity(accept, prefer, ifMatch, entitySetName, entityId, changes);
-  }
-
-  @MERGE
   @Path("/{entitySetName}({entityId})")
   @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
   @Consumes({MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
@@ -204,20 +194,6 @@ public abstract class AbstractServices {
   }
 
   @PATCH
-  @Path("/{entitySetName}/{entityId}")
-  @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
-  @Consumes({MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
-  public Response patchEntityKeyAsSegment(
-          @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
-          @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
-          @HeaderParam("If-Match") @DefaultValue(StringUtils.EMPTY) String ifMatch,
-          @PathParam("entitySetName") String entitySetName,
-          @PathParam("entityId") String entityId,
-          final String changes) {
-    return patchEntity(accept, prefer, ifMatch, entitySetName, entityId, changes);
-  }
-
-  @PATCH
   @Path("/{entitySetName}({entityId})")
   @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
   @Consumes({MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
@@ -230,7 +206,7 @@ public abstract class AbstractServices {
           final String changes) {
 
     try {
-      final Accept acceptType = Accept.parse(accept, getVersion());
+      final Accept acceptType = Accept.parse(accept, version);
 
       if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
         throw new UnsupportedMediaTypeException("Unsupported media type");
@@ -259,19 +235,6 @@ public abstract class AbstractServices {
   }
 
   @PUT
-  @Path("/{entitySetName}/{entityId}")
-  @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
-  @Consumes({MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
-  public Response putNewEntityKeyAsSegment(
-          @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
-          @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
-          @PathParam("entitySetName") String entitySetName,
-          @PathParam("entityId") String entityId,
-          final String entity) {
-    return replaceEntity(accept, prefer, entitySetName, entityId, entity);
-  }
-
-  @PUT
   @Path("/{entitySetName}({entityId})")
   @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
   @Consumes({MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
@@ -282,7 +245,7 @@ public abstract class AbstractServices {
           @PathParam("entityId") String entityId,
           final String entity) {
     try {
-      final Accept acceptType = Accept.parse(accept, getVersion());
+      final Accept acceptType = Accept.parse(accept, version);
 
       if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
         throw new UnsupportedMediaTypeException("Unsupported media type");
@@ -326,7 +289,7 @@ public abstract class AbstractServices {
     // default
     AbstractUtilities utils = xml;
     try {
-      final Accept acceptType = Accept.parse(accept, getVersion());
+      final Accept acceptType = Accept.parse(accept, version);
 
       if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
         throw new UnsupportedMediaTypeException("Unsupported media type");
@@ -360,6 +323,139 @@ public abstract class AbstractServices {
     }
   }
 
+  @POST
+  @Path("/Person({entityId})/{type:.*}/Sack")
+  public Response actionSack(
+          @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
+          @PathParam("entityId") final String entityId,
+          @PathParam("type") final String type,
+          @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {
+
+    final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format);
+
+    if (utils.getKey() == Accept.XML || utils.getKey() == Accept.TEXT) {
+      throw new UnsupportedMediaTypeException("Unsupported media type");
+    }
+
+    final Map.Entry<String, InputStream> entityInfo = utils.getValue().readEntity("Person", entityId, utils.getKey());
+
+    InputStream entity = entityInfo.getValue();
+    try {
+      final ByteArrayOutputStream copy = new ByteArrayOutputStream();
+      IOUtils.copy(entity, copy);
+      IOUtils.closeQuietly(entity);
+
+      final String newContent = new String(copy.toByteArray(), "UTF-8").
+              replaceAll("\"Salary\":[0-9]*,", "\"Salary\":0,").
+              replaceAll("\"Title\":\".*\"", "\"Title\":\"[Sacked]\"").
+              replaceAll("\\<d:Salary m:type=\"Edm.Int32\"\\>.*\\</d:Salary\\>",
+                      "<d:Salary m:type=\"Edm.Int32\">0</d:Salary>").
+              replaceAll("\\<d:Title\\>.*\\</d:Title\\>", "<d:Title>[Sacked]</d:Title>");
+
+      final FSManager fsManager = FSManager.instance(version);
+      fsManager.putInMemory(IOUtils.toInputStream(newContent, "UTF-8"),
+              fsManager.getAbsolutePath(Commons.getEntityBasePath("Person", entityId) + Constants.get(version,
+                              ConstantKey.ENTITY), utils.getKey()));
+
+      return utils.getValue().createResponse(null, null, utils.getKey(), Response.Status.NO_CONTENT);
+    } catch (Exception e) {
+      return xml.createFaultResponse(accept, e);
+    }
+  }
+
+  @POST
+  @Path("/Person/{type:.*}/IncreaseSalaries")
+  public Response actionIncreaseSalaries(
+          @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
+          @PathParam("type") final String type,
+          @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format,
+          final String body) {
+
+    final String name = "Person";
+    try {
+      final Accept acceptType = Accept.parse(accept, version);
+      if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
+        throw new UnsupportedMediaTypeException("Unsupported media type");
+      }
+
+      final JsonNode tree = new ObjectMapper().readTree(body);
+      if (!tree.has("n")) {
+        throw new Exception("Missing parameter: n");
+      }
+      final int n = tree.get("n").asInt();
+
+      final StringBuilder path = new StringBuilder(name).
+              append(File.separatorChar).append(type).
+              append(File.separatorChar);
+      path.append(Commons.getLinkInfo().get(version).isSingleton(name)
+              ? Constants.get(version, ConstantKey.ENTITY)
+              : Constants.get(version, ConstantKey.FEED));
+
+      final InputStream feed = FSManager.instance(version).readFile(path.toString(), acceptType);
+
+      final ByteArrayOutputStream copy = new ByteArrayOutputStream();
+      IOUtils.copy(feed, copy);
+      IOUtils.closeQuietly(feed);
+
+      String newContent = new String(copy.toByteArray(), "UTF-8");
+      final Pattern salary = Pattern.compile(acceptType == Accept.ATOM
+              ? "\\<d:Salary m:type=\"Edm.Int32\"\\>(-?\\d+)\\</d:Salary\\>"
+              : "\"Salary\":(-?\\d+),");
+      final Matcher salaryMatcher = salary.matcher(newContent);
+      while (salaryMatcher.find()) {
+        final Long newSalary = Long.valueOf(salaryMatcher.group(1)) + n;
+        newContent = newContent.
+                replaceAll("\"Salary\":" + salaryMatcher.group(1) + ",",
+                        "\"Salary\":" + newSalary + ",").
+                replaceAll("\\<d:Salary m:type=\"Edm.Int32\"\\>" + salaryMatcher.group(1) + "</d:Salary\\>",
+                        "<d:Salary m:type=\"Edm.Int32\">" + newSalary + "</d:Salary>");
+      }
+
+      FSManager.instance(version).putInMemory(IOUtils.toInputStream(newContent, "UTF-8"),
+              FSManager.instance(version).getAbsolutePath(path.toString(), acceptType));
+
+      return xml.createResponse(null, null, acceptType, Response.Status.NO_CONTENT);
+    } catch (Exception e) {
+      return xml.createFaultResponse(accept, e);
+    }
+  }
+
+  /**
+   * Retrieve entities from the given entity set and the given type.
+   *
+   * @param accept Accept header.
+   * @param name entity set.
+   * @param type entity type.
+   * @return entity set.
+   */
+  @GET
+  @Path("/{name}/{type:.*}")
+  public Response getEntitySet(
+          @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
+          @PathParam("name") final String name,
+          @PathParam("type") final String type) {
+
+    try {
+      final Accept acceptType = Accept.parse(accept, version);
+      if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
+        throw new UnsupportedMediaTypeException("Unsupported media type");
+      }
+
+      final String basePath = name + File.separatorChar;
+      final StringBuilder path = new StringBuilder(name).
+              append(File.separatorChar).append(type).
+              append(File.separatorChar);
+      path.append(Commons.getLinkInfo().get(version).isSingleton(name)
+              ? Constants.get(version, ConstantKey.ENTITY)
+              : Constants.get(version, ConstantKey.FEED));
+
+      final InputStream feed = FSManager.instance(version).readFile(path.toString(), acceptType);
+      return xml.createResponse(feed, Commons.getETag(basePath, version), acceptType);
+    } catch (Exception e) {
+      return xml.createFaultResponse(accept, e);
+    }
+  }
+
   /**
    * Retrieve entity set or function execution sample.
    *
@@ -388,18 +484,18 @@ public abstract class AbstractServices {
       if (StringUtils.isNotBlank(format)) {
         acceptType = Accept.valueOf(format.toUpperCase());
       } else {
-        acceptType = Accept.parse(accept, getVersion());
-      }
-
-      if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
-        throw new UnsupportedMediaTypeException("Unsupported media type");
+        acceptType = Accept.parse(accept, version);
       }
 
       try {
         // search for function ...
-        final InputStream func = FSManager.instance(getVersion()).readFile(name, acceptType);
+        final InputStream func = FSManager.instance(version).readFile(name, acceptType);
         return xml.createResponse(func, null, acceptType);
       } catch (NotFoundException e) {
+        if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
+          throw new UnsupportedMediaTypeException("Unsupported media type");
+        }
+
         // search for entitySet ...
         final String basePath = name + File.separatorChar;
 
@@ -407,67 +503,75 @@ public abstract class AbstractServices {
         builder.append(basePath);
 
         if (StringUtils.isNotBlank(orderby)) {
-          builder.append(Constants.get(getVersion(), ConstantKey.ORDERBY)).append(File.separatorChar).
+          builder.append(Constants.get(version, ConstantKey.ORDERBY)).append(File.separatorChar).
                   append(orderby).append(File.separatorChar);
         }
 
         if (StringUtils.isNotBlank(filter)) {
-          builder.append(Constants.get(getVersion(), ConstantKey.FILTER)).append(File.separatorChar).
+          builder.append(Constants.get(version, ConstantKey.FILTER)).append(File.separatorChar).
                   append(filter.replaceAll("/", "."));
         } else if (StringUtils.isNotBlank(skiptoken)) {
-          builder.append(Constants.get(getVersion(), ConstantKey.SKIP_TOKEN)).append(File.separatorChar).
+          builder.append(Constants.get(version, ConstantKey.SKIP_TOKEN)).append(File.separatorChar).
                   append(skiptoken);
         } else {
-          builder.append(Commons.getLinkInfo().get(getVersion()).isSingleton(name)
-                  ? Constants.get(getVersion(), ConstantKey.ENTITY)
-                  : Constants.get(getVersion(), ConstantKey.FEED));
+          builder.append(Commons.getLinkInfo().get(version).isSingleton(name)
+                  ? Constants.get(version, ConstantKey.ENTITY)
+                  : Constants.get(version, ConstantKey.FEED));
         }
 
-        InputStream feed = FSManager.instance(getVersion()).readFile(builder.toString(), acceptType);
+        InputStream feed = FSManager.instance(version).readFile(builder.toString(), acceptType);
         if ("allpages".equals(inlinecount)) {
           int count = xml.countAllElements(name);
           feed.close();
           if (acceptType == Accept.ATOM) {
             feed = xml.addAtomInlinecount(
-                    FSManager.instance(getVersion()).readFile(builder.toString(), acceptType),
+                    FSManager.instance(version).readFile(builder.toString(), acceptType),
                     count,
                     acceptType);
           } else {
             feed = json.addJsonInlinecount(
-                    FSManager.instance(getVersion()).readFile(builder.toString(), acceptType),
+                    FSManager.instance(version).readFile(builder.toString(), acceptType),
                     count,
                     acceptType);
           }
         }
 
-        return xml.createResponse(feed, Commons.getETag(basePath, getVersion()), acceptType);
+        return xml.createResponse(feed, Commons.getETag(basePath, version), acceptType);
       }
     } catch (Exception e) {
       return xml.createFaultResponse(accept, e);
     }
   }
 
-  /**
-   * Retrieve entity with key as segment.
-   *
-   * @param accept Accept header.
-   * @param entitySetName Entity set name.
-   * @param entityId entity id.
-   * @param format format query option.
-   * @param expand expand query option.
-   * @param select select query option.
-   * @return entity.
-   */
   @GET
-  @Path("/{entitySetName}/{entityId}")
-  public Response getEntityKeyAsSegment(
-          @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
-          @PathParam("entitySetName") String entitySetName,
-          @PathParam("entityId") String entityId,
-          @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) String format,
-          @QueryParam("$expand") @DefaultValue(StringUtils.EMPTY) String expand,
-          @QueryParam("$select") @DefaultValue(StringUtils.EMPTY) String select) {
-    return getEntity(accept, entitySetName, entityId, format, expand, select, true);
+  @Path("/Person({entityId})")
+  public Response getEntity(
+          @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
+          @PathParam("entityId") final String entityId,
+          @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {
+
+    final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format);
+
+    if (utils.getKey() == Accept.XML || utils.getKey() == Accept.TEXT) {
+      throw new UnsupportedMediaTypeException("Unsupported media type");
+    }
+
+    final Map.Entry<String, InputStream> entityInfo = utils.getValue().readEntity("Person", entityId, utils.getKey());
+
+    InputStream entity = entityInfo.getValue();
+    try {
+      if (utils.getKey() == Accept.JSON_FULLMETA || utils.getKey() == Accept.ATOM) {
+        entity = utils.getValue().addOperation(entity, "Sack", "#DefaultContainer.Sack",
+                uriInfo.getAbsolutePath().toASCIIString()
+                + "/Microsoft.Test.OData.Services.AstoriaDefaultService.SpecialEmployee/Sack");
+      }
+
+      return utils.getValue().createResponse(
+              entity, Commons.getETag(entityInfo.getKey(), version), utils.getKey());
+    } catch (Exception e) {
+      LOG.error("Error retrieving entity", e);
+      return xml.createFaultResponse(accept, e);
+    }
   }
 
   /**
@@ -491,10 +595,10 @@ public abstract class AbstractServices {
           @QueryParam("$expand") @DefaultValue(StringUtils.EMPTY) String expand,
           @QueryParam("$select") @DefaultValue(StringUtils.EMPTY) String select) {
 
-    return getEntity(accept, entitySetName, entityId, format, expand, select, false);
+    return getEntityInternal(accept, entitySetName, entityId, format, expand, select, false);
   }
 
-  public Response getEntity(
+  protected Response getEntityInternal(
           final String accept,
           final String entitySetName,
           final String entityId,
@@ -518,7 +622,7 @@ public abstract class AbstractServices {
       if (keyAsSegment) {
         entity = utils.getValue().addEditLink(
                 entity, entitySetName,
-                Constants.get(getVersion(), ConstantKey.DEFAULT_SERVICE_URL) + entitySetName + "/" + entityId);
+                Constants.get(version, ConstantKey.DEFAULT_SERVICE_URL) + entitySetName + "/" + entityId);
       }
 
       if (StringUtils.isNotBlank(select)) {
@@ -535,9 +639,7 @@ public abstract class AbstractServices {
         }
       }
 
-      return utils.getValue().createResponse(
-              entity, Commons.getETag(entityInfo.getKey(), getVersion()), utils.getKey());
-
+      return utils.getValue().createResponse(entity, Commons.getETag(entityInfo.getKey(), version), utils.getKey());
     } catch (Exception e) {
       LOG.error("Error retrieving entity", e);
       return xml.createFaultResponse(accept, e);
@@ -558,7 +660,7 @@ public abstract class AbstractServices {
 
       final AbstractUtilities utils = getUtilities(null);
       final Map.Entry<String, InputStream> entityInfo = utils.readMediaEntity(entitySetName, entityId);
-      return utils.createResponse(entityInfo.getValue(), Commons.getETag(entityInfo.getKey(), getVersion()), null);
+      return utils.createResponse(entityInfo.getValue(), Commons.getETag(entityInfo.getKey(), version), null);
 
     } catch (Exception e) {
       LOG.error("Error retrieving entity", e);
@@ -567,27 +669,20 @@ public abstract class AbstractServices {
   }
 
   @DELETE
-  @Path("/{entitySetName}/{entityId}")
-  public Response removeEntityKeyAsSegment(
-          @PathParam("entitySetName") String entitySetName,
-          @PathParam("entityId") String entityId) {
-    return removeEntity(entitySetName, entityId);
-  }
-
-  @DELETE
   @Path("/{entitySetName}({entityId})")
   public Response removeEntity(
           @PathParam("entitySetName") String entitySetName,
           @PathParam("entityId") String entityId) {
+
     try {
       final String basePath =
               entitySetName + File.separatorChar + Commons.getEntityKey(entityId) + File.separatorChar;
 
-      FSManager.instance(getVersion()).deleteFile(basePath + Constants.get(getVersion(), ConstantKey.ENTITY));
+      FSManager.instance(version).deleteFile(basePath + Constants.get(version, ConstantKey.ENTITY));
 
       return xml.createResponse(null, null, null, Response.Status.NO_CONTENT);
     } catch (Exception e) {
-      return xml.createFaultResponse(Accept.XML.toString(getVersion()), e);
+      return xml.createFaultResponse(Accept.XML.toString(version), e);
     }
   }
 
@@ -605,7 +700,7 @@ public abstract class AbstractServices {
       if (StringUtils.isNotBlank(format)) {
         acceptType = Accept.valueOf(format.toUpperCase());
       } else if (StringUtils.isNotBlank(accept)) {
-        acceptType = Accept.parse(accept, getVersion(), null);
+        acceptType = Accept.parse(accept, version, null);
       }
 
       // if the given path is not about any link then search for property
@@ -652,7 +747,7 @@ public abstract class AbstractServices {
       if (StringUtils.isNotBlank(format)) {
         acceptType = Accept.valueOf(format.toUpperCase());
       } else if (StringUtils.isNotBlank(accept)) {
-        acceptType = Accept.parse(accept, getVersion(), null);
+        acceptType = Accept.parse(accept, version, null);
       }
 
       // if the given path is not about any link then search for property
@@ -789,7 +884,7 @@ public abstract class AbstractServices {
 
     } catch (Exception e) {
       LOG.error("Error retrieving entity", e);
-      return xml.createFaultResponse(Accept.JSON.toString(getVersion()), e);
+      return xml.createFaultResponse(Accept.JSON.toString(version), e);
     }
   }
 
@@ -848,7 +943,7 @@ public abstract class AbstractServices {
 
     } catch (Exception e) {
       LOG.error("Error retrieving entity", e);
-      return xml.createFaultResponse(Accept.JSON.toString(getVersion()), e);
+      return xml.createFaultResponse(Accept.JSON.toString(version), e);
     }
   }
 
@@ -900,7 +995,7 @@ public abstract class AbstractServices {
       if (StringUtils.isNotBlank(format)) {
         acceptType = Accept.valueOf(format.toUpperCase());
       } else if (StringUtils.isNotBlank(accept)) {
-        acceptType = Accept.parse(accept, getVersion(), null);
+        acceptType = Accept.parse(accept, version, null);
       }
       utils = getUtilities(acceptType);
 
@@ -941,7 +1036,7 @@ public abstract class AbstractServices {
         if (StringUtils.isNotBlank(format)) {
           acceptType = Accept.valueOf(format.toUpperCase());
         } else if (StringUtils.isNotBlank(accept)) {
-          acceptType = Accept.parse(accept, getVersion(), null);
+          acceptType = Accept.parse(accept, version, null);
         }
 
         try {
@@ -963,7 +1058,7 @@ public abstract class AbstractServices {
 
     final AbstractUtilities utils = getUtilities(null);
     final Map.Entry<String, InputStream> entityInfo = utils.readMediaEntity(entitySetName, entityId, path);
-    return utils.createResponse(entityInfo.getValue(), Commons.getETag(entityInfo.getKey(), getVersion()), null);
+    return utils.createResponse(entityInfo.getValue(), Commons.getETag(entityInfo.getKey(), version), null);
   }
 
   private Response navigateEntity(
@@ -989,7 +1084,7 @@ public abstract class AbstractServices {
         stream = xml.readEntities(links.getValue(), path, links.getKey(), linkInfo.isFeed());
     }
 
-    return xml.createResponse(stream, Commons.getETag(basePath, getVersion()), acceptType);
+    return xml.createResponse(stream, Commons.getETag(basePath, version), acceptType);
   }
 
   private Response navigateProperty(
@@ -1012,8 +1107,8 @@ public abstract class AbstractServices {
     InputStream stream;
 
     if (searchForValue) {
-      stream = FSManager.instance(getVersion()).readFile(
-              basePath + Constants.get(getVersion(), ConstantKey.ENTITY),
+      stream = FSManager.instance(version).readFile(
+              basePath + Constants.get(version, ConstantKey.ENTITY),
               acceptType == null || acceptType == Accept.TEXT ? Accept.XML : acceptType);
 
       stream = utils.getPropertyValue(stream, pathElements);
@@ -1022,7 +1117,7 @@ public abstract class AbstractServices {
       stream = utils.getProperty(entitySetName, entityId, pathElements, edmType);
     }
 
-    return xml.createResponse(stream, Commons.getETag(basePath, getVersion()), acceptType);
+    return xml.createResponse(stream, Commons.getETag(basePath, version), acceptType);
   }
 
   /**
@@ -1048,7 +1143,7 @@ public abstract class AbstractServices {
       if (StringUtils.isNotBlank(format)) {
         acceptType = Accept.valueOf(format.toUpperCase());
       } else {
-        acceptType = Accept.parse(accept, getVersion());
+        acceptType = Accept.parse(accept, version);
       }
 
       if (acceptType == Accept.ATOM) {
@@ -1081,7 +1176,7 @@ public abstract class AbstractServices {
       if (StringUtils.isNotBlank(format)) {
         acceptType = Accept.valueOf(format.toUpperCase());
       } else {
-        acceptType = Accept.parse(accept, getVersion());
+        acceptType = Accept.parse(accept, version);
       }
 
       if (acceptType == Accept.ATOM) {
@@ -1090,7 +1185,7 @@ public abstract class AbstractServices {
 
       final Accept content;
       if (StringUtils.isNotBlank(contentType)) {
-        content = Accept.parse(contentType, getVersion());
+        content = Accept.parse(contentType, version);
       } else {
         content = acceptType;
       }
@@ -1158,7 +1253,7 @@ public abstract class AbstractServices {
       if (StringUtils.isNotBlank(format)) {
         acceptType = Accept.valueOf(format.toUpperCase());
       } else {
-        acceptType = Accept.parse(accept, getVersion());
+        acceptType = Accept.parse(accept, version);
       }
 
       if (acceptType == Accept.ATOM) {
@@ -1167,7 +1262,7 @@ public abstract class AbstractServices {
 
       final Accept content;
       if (StringUtils.isNotBlank(contentType)) {
-        content = Accept.parse(contentType, getVersion());
+        content = Accept.parse(contentType, version);
       } else {
         content = acceptType;
       }
@@ -1208,7 +1303,7 @@ public abstract class AbstractServices {
       if (StringUtils.isNotBlank(format)) {
         acceptType = Accept.valueOf(format.toUpperCase());
       } else {
-        acceptType = Accept.parse(accept, getVersion());
+        acceptType = Accept.parse(accept, version);
       }
 
       if (acceptType == Accept.ATOM) {
@@ -1251,7 +1346,7 @@ public abstract class AbstractServices {
           @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
           @PathParam("entitySetName") String entitySetName) {
     try {
-      final Accept acceptType = Accept.parse(accept, getVersion(), Accept.TEXT);
+      final Accept acceptType = Accept.parse(accept, version, Accept.TEXT);
 
       if (acceptType != Accept.TEXT) {
         throw new UnsupportedMediaTypeException("Unsupported type " + accept);
@@ -1273,7 +1368,7 @@ public abstract class AbstractServices {
     if (StringUtils.isNotBlank(format)) {
       acceptType = Accept.valueOf(format.toUpperCase());
     } else {
-      acceptType = Accept.parse(accept, getVersion());
+      acceptType = Accept.parse(accept, version);
     }
 
     return new AbstractMap.SimpleEntry<Accept, AbstractUtilities>(acceptType, getUtilities(acceptType));

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/bb748ec0/fit/src/main/java/org/apache/olingo/fit/V3KeyAsSegment.java
----------------------------------------------------------------------
diff --git a/fit/src/main/java/org/apache/olingo/fit/V3KeyAsSegment.java b/fit/src/main/java/org/apache/olingo/fit/V3KeyAsSegment.java
new file mode 100644
index 0000000..6ec8651
--- /dev/null
+++ b/fit/src/main/java/org/apache/olingo/fit/V3KeyAsSegment.java
@@ -0,0 +1,159 @@
+/*
+ * 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;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import static javax.ws.rs.core.Response.status;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.olingo.fit.methods.MERGE;
+import org.apache.olingo.fit.methods.PATCH;
+import org.springframework.stereotype.Service;
+
+@Service
+@Path("/V30/KeyAsSegment.svc")
+public class V3KeyAsSegment {
+
+  private final V3Services services;
+
+  public V3KeyAsSegment() throws Exception {
+    this.services = new V3Services();
+  }
+
+  private Response replaceServiceName(final Response response) {
+    try {
+      final String content = IOUtils.toString((InputStream) response.getEntity(), "UTF-8").
+              replaceAll("Static\\.svc", "KeyAsSegment.svc");
+
+      final Response.ResponseBuilder builder = status(response.getStatus());
+      for (String headerName : response.getHeaders().keySet()) {
+        for (Object headerValue : response.getHeaders().get(headerName)) {
+          builder.header(headerName, headerValue);
+        }
+      }
+
+      final InputStream toBeStreamedBack = IOUtils.toInputStream(content, "UTF-8");
+      final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      IOUtils.copy(toBeStreamedBack, baos);
+      IOUtils.closeQuietly(toBeStreamedBack);
+
+      builder.header("Content-Length", baos.size());
+      builder.entity(new ByteArrayInputStream(baos.toByteArray()));
+
+      return builder.build();
+    } catch (Exception e) {
+      return response;
+    }
+  }
+
+  @GET
+  @Path("/{entitySetName}/{entityId}")
+  public Response getEntity(
+          @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
+          @PathParam("entitySetName") String entitySetName,
+          @PathParam("entityId") String entityId,
+          @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) String format,
+          @QueryParam("$expand") @DefaultValue(StringUtils.EMPTY) String expand,
+          @QueryParam("$select") @DefaultValue(StringUtils.EMPTY) String select) {
+
+    return replaceServiceName(services.getEntityInternal(
+            accept, entitySetName, entityId, format, expand, select, true));
+  }
+
+  @DELETE
+  @Path("/{entitySetName}/{entityId}")
+  public Response removeEntity(
+          @PathParam("entitySetName") String entitySetName,
+          @PathParam("entityId") String entityId) {
+
+    return replaceServiceName(services.removeEntity(entitySetName, entityId));
+  }
+
+  @MERGE
+  @Path("/{entitySetName}/{entityId}")
+  @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
+  @Consumes({MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
+  public Response mergeEntity(
+          @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
+          @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
+          @HeaderParam("If-Match") @DefaultValue(StringUtils.EMPTY) String ifMatch,
+          @PathParam("entitySetName") String entitySetName,
+          @PathParam("entityId") String entityId,
+          final String changes) {
+
+    return replaceServiceName(services.patchEntity(accept, prefer, ifMatch, entitySetName, entityId, changes));
+  }
+
+  @PATCH
+  @Path("/{entitySetName}/{entityId}")
+  @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
+  @Consumes({MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
+  public Response patchEntity(
+          @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
+          @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
+          @HeaderParam("If-Match") @DefaultValue(StringUtils.EMPTY) String ifMatch,
+          @PathParam("entitySetName") String entitySetName,
+          @PathParam("entityId") String entityId,
+          final String changes) {
+
+    return replaceServiceName(services.patchEntity(accept, prefer, ifMatch, entitySetName, entityId, changes));
+  }
+
+  @PUT
+  @Path("/{entitySetName}/{entityId}")
+  @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
+  @Consumes({MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
+  public Response putNewEntity(
+          @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
+          @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
+          @PathParam("entitySetName") String entitySetName,
+          @PathParam("entityId") String entityId,
+          final String entity) {
+
+    return replaceServiceName(services.replaceEntity(accept, prefer, entitySetName, entityId, entity));
+  }
+
+  @POST
+  @Path("/{entitySetName}")
+  @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
+  @Consumes({MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM})
+  public Response postNewEntity(
+          @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
+          @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
+          @PathParam("entitySetName") String entitySetName,
+          final String entity) {
+
+    return replaceServiceName(services.postNewEntity(accept, prefer, entitySetName, entity));
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/bb748ec0/fit/src/main/java/org/apache/olingo/fit/V3Services.java
----------------------------------------------------------------------
diff --git a/fit/src/main/java/org/apache/olingo/fit/V3Services.java b/fit/src/main/java/org/apache/olingo/fit/V3Services.java
index 916ca82..1f379d3 100644
--- a/fit/src/main/java/org/apache/olingo/fit/V3Services.java
+++ b/fit/src/main/java/org/apache/olingo/fit/V3Services.java
@@ -28,18 +28,15 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.cxf.interceptor.InInterceptors;
 import org.apache.olingo.fit.utils.ConstantKey;
 import org.apache.olingo.fit.utils.Constants;
+import org.springframework.stereotype.Service;
 
+@Service
 @Path("/V30/Static.svc")
 @InInterceptors(classes = XHTTPMethodInterceptor.class)
 public class V3Services extends AbstractServices {
 
   public V3Services() throws Exception {
-    super();
-  }
-
-  @Override
-  protected ODataVersion getVersion() {
-    return ODataVersion.v3;
+    super(ODataVersion.v3);
   }
 
   /**
@@ -51,7 +48,7 @@ public class V3Services extends AbstractServices {
   @Path("/large/$metadata")
   @Produces("application/xml")
   public Response getLargeMetadata() {
-    return getMetadata("large" + StringUtils.capitalize(Constants.get(getVersion(), ConstantKey.METADATA)));
+    return getMetadata("large" + StringUtils.capitalize(Constants.get(version, ConstantKey.METADATA)));
   }
 
   /**
@@ -63,7 +60,7 @@ public class V3Services extends AbstractServices {
   @Path("/openType/$metadata")
   @Produces("application/xml")
   public Response getOpenTypeMetadata() {
-    return getMetadata("openType" + StringUtils.capitalize(Constants.get(getVersion(), ConstantKey.METADATA)));
+    return getMetadata("openType" + StringUtils.capitalize(Constants.get(version, ConstantKey.METADATA)));
   }
 
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/bb748ec0/fit/src/main/java/org/apache/olingo/fit/V4NorthWind.java
----------------------------------------------------------------------
diff --git a/fit/src/main/java/org/apache/olingo/fit/V4NorthWind.java b/fit/src/main/java/org/apache/olingo/fit/V4NorthWind.java
index b11d3e5..254b5ae 100644
--- a/fit/src/main/java/org/apache/olingo/fit/V4NorthWind.java
+++ b/fit/src/main/java/org/apache/olingo/fit/V4NorthWind.java
@@ -25,23 +25,20 @@ import javax.ws.rs.core.Response;
 import org.apache.cxf.interceptor.InInterceptors;
 import org.apache.olingo.fit.utils.ConstantKey;
 import org.apache.olingo.fit.utils.Constants;
+import org.springframework.stereotype.Service;
 
+@Service
 @Path("/V40/NorthWind.svc")
 @InInterceptors(classes = XHTTPMethodInterceptor.class)
 public class V4NorthWind extends AbstractServices {
 
   public V4NorthWind() throws Exception {
-    super();
-  }
-
-  @Override
-  protected ODataVersion getVersion() {
-    return ODataVersion.v4;
+    super(ODataVersion.v4);
   }
 
   @Override
   public Response getMetadata() {
-    return getMetadata("northwind-" + Constants.get(getVersion(), ConstantKey.METADATA));
+    return getMetadata("northwind-" + Constants.get(version, ConstantKey.METADATA));
   }
 
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/bb748ec0/fit/src/main/java/org/apache/olingo/fit/V4NorthWindExt.java
----------------------------------------------------------------------
diff --git a/fit/src/main/java/org/apache/olingo/fit/V4NorthWindExt.java b/fit/src/main/java/org/apache/olingo/fit/V4NorthWindExt.java
index b5d4bda..ba986f0 100644
--- a/fit/src/main/java/org/apache/olingo/fit/V4NorthWindExt.java
+++ b/fit/src/main/java/org/apache/olingo/fit/V4NorthWindExt.java
@@ -26,23 +26,20 @@ import org.apache.cxf.interceptor.InInterceptors;
 import org.apache.olingo.fit.utils.ConstantKey;
 import org.apache.olingo.fit.utils.Constants;
 import org.apache.olingo.fit.utils.ResolvingReferencesInterceptor;
+import org.springframework.stereotype.Service;
 
+@Service
 @Path("/V40/NorthWindExt.svc")
 @InInterceptors(classes = {XHTTPMethodInterceptor.class, ResolvingReferencesInterceptor.class})
 public class V4NorthWindExt extends AbstractServices {
 
   public V4NorthWindExt() throws Exception {
-    super();
-  }
-
-  @Override
-  protected ODataVersion getVersion() {
-    return ODataVersion.v4;
+    super(ODataVersion.v4);
   }
 
   @Override
   public Response getMetadata() {
-    return getMetadata("northwindExt-" + Constants.get(getVersion(), ConstantKey.METADATA));
+    return getMetadata("northwindExt-" + Constants.get(version, ConstantKey.METADATA));
   }
 
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/bb748ec0/fit/src/main/java/org/apache/olingo/fit/V4Services.java
----------------------------------------------------------------------
diff --git a/fit/src/main/java/org/apache/olingo/fit/V4Services.java b/fit/src/main/java/org/apache/olingo/fit/V4Services.java
index 327e82d..838a4f6 100644
--- a/fit/src/main/java/org/apache/olingo/fit/V4Services.java
+++ b/fit/src/main/java/org/apache/olingo/fit/V4Services.java
@@ -23,17 +23,14 @@ import org.apache.olingo.fit.utils.XHTTPMethodInterceptor;
 import javax.ws.rs.Path;
 import org.apache.cxf.interceptor.InInterceptors;
 import org.apache.olingo.fit.utils.ResolvingReferencesInterceptor;
+import org.springframework.stereotype.Service;
 
+@Service
 @Path("/V40/Static.svc")
 @InInterceptors(classes = {XHTTPMethodInterceptor.class, ResolvingReferencesInterceptor.class})
 public class V4Services extends AbstractServices {
 
   public V4Services() throws Exception {
-    super();
-  }
-
-  @Override
-  protected ODataVersion getVersion() {
-    return ODataVersion.v4;
+    super(ODataVersion.v4);
   }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/bb748ec0/fit/src/main/java/org/apache/olingo/fit/utils/AbstractJSONUtilities.java
----------------------------------------------------------------------
diff --git a/fit/src/main/java/org/apache/olingo/fit/utils/AbstractJSONUtilities.java b/fit/src/main/java/org/apache/olingo/fit/utils/AbstractJSONUtilities.java
index 14fff09..6c6b7cc 100644
--- a/fit/src/main/java/org/apache/olingo/fit/utils/AbstractJSONUtilities.java
+++ b/fit/src/main/java/org/apache/olingo/fit/utils/AbstractJSONUtilities.java
@@ -140,6 +140,7 @@ public abstract class AbstractJSONUtilities extends AbstractUtilities {
   protected InputStream normalizeLinks(
           final String entitySetName, final String entityKey, final InputStream is, final NavigationLinks links)
           throws Exception {
+
     final ObjectMapper mapper = new ObjectMapper();
     final ObjectNode srcNode = (ObjectNode) mapper.readTree(is);
 
@@ -169,7 +170,7 @@ public abstract class AbstractJSONUtilities extends AbstractUtilities {
 
     srcNode.set(
             Constants.get(version, ConstantKey.JSON_EDITLINK_NAME), new TextNode(
-            Constants.get(version, ConstantKey.DEFAULT_SERVICE_URL) + entitySetName + "(" + entityKey + ")"));
+                    Constants.get(version, ConstantKey.DEFAULT_SERVICE_URL) + entitySetName + "(" + entityKey + ")"));
 
     return IOUtils.toInputStream(srcNode.toString(), "UTf-8");
   }
@@ -326,9 +327,9 @@ public abstract class AbstractJSONUtilities extends AbstractUtilities {
 
     for (String link : links) {
       try {
-        final Map.Entry<String, String> uri = Commons.parseEntityURI(link);
+        final Map.Entry<String, String> uriMap = Commons.parseEntityURI(link);
         final Map.Entry<String, InputStream> entity =
-                readEntity(uri.getKey(), uri.getValue(), Accept.JSON_FULLMETA);
+                readEntity(uriMap.getKey(), uriMap.getValue(), Accept.JSON_FULLMETA);
 
         if (bos.size() > 1) {
           bos.write(",".getBytes());
@@ -452,7 +453,23 @@ public abstract class AbstractJSONUtilities extends AbstractUtilities {
     IOUtils.closeQuietly(content);
 
     srcNode.set(Constants.get(version, ConstantKey.JSON_EDITLINK_NAME), new TextNode(href));
-    return IOUtils.toInputStream(srcNode.toString(), "UTf-8");
+    return IOUtils.toInputStream(srcNode.toString(), "UTF-8");
+  }
+
+  @Override
+  public InputStream addOperation(final InputStream content, final String name, final String metaAnchor,
+          final String href) throws Exception {
+
+    final ObjectMapper mapper = new ObjectMapper();
+    final ObjectNode srcNode = (ObjectNode) mapper.readTree(content);
+    IOUtils.closeQuietly(content);
+
+    final ObjectNode action = mapper.createObjectNode();
+    action.set("title", new TextNode(name));
+    action.set("target", new TextNode(href));
+
+    srcNode.set(metaAnchor, action);
+    return IOUtils.toInputStream(srcNode.toString(), "UTF-8");
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/bb748ec0/fit/src/main/java/org/apache/olingo/fit/utils/AbstractUtilities.java
----------------------------------------------------------------------
diff --git a/fit/src/main/java/org/apache/olingo/fit/utils/AbstractUtilities.java b/fit/src/main/java/org/apache/olingo/fit/utils/AbstractUtilities.java
index 8df97e3..360a106 100644
--- a/fit/src/main/java/org/apache/olingo/fit/utils/AbstractUtilities.java
+++ b/fit/src/main/java/org/apache/olingo/fit/utils/AbstractUtilities.java
@@ -120,6 +120,7 @@ public abstract class AbstractUtilities {
           final String key,
           final String entitySetName,
           final InputStream is) throws Exception {
+
     return saveSingleEntity(key, entitySetName, is, null);
   }
 
@@ -154,6 +155,7 @@ public abstract class AbstractUtilities {
 
   public InputStream addOrReplaceEntity(
           final String entitySetName, final InputStream is) throws Exception {
+
     return addOrReplaceEntity(null, entitySetName, is);
   }
 
@@ -332,7 +334,7 @@ public abstract class AbstractUtilities {
 
     fsManager.putInMemory(
             IOUtils.toInputStream(entity), fsManager.getAbsolutePath(path + Constants.get(version, ConstantKey.ENTITY),
-            Accept.JSON_FULLMETA));
+                    Accept.JSON_FULLMETA));
     // -----------------------------------------
 
     return readEntity(entitySetName, entityKey, getDefaultFormat()).getValue();
@@ -344,6 +346,7 @@ public abstract class AbstractUtilities {
           final String entityKey,
           final String linkName,
           final Collection<String> links) throws IOException {
+
     final HashSet<String> uris = new HashSet<String>();
 
     if (Commons.linkInfo.get(version).isFeed(entitySetName, linkName)) {
@@ -362,6 +365,7 @@ public abstract class AbstractUtilities {
   public void putLinksInMemory(
           final String basePath, final String entitySetName, final String linkName, final Collection<String> uris)
           throws IOException {
+
     fsManager.putInMemory(
             Commons.getLinksAsJSON(entitySetName, new SimpleEntry<String, Collection<String>>(linkName, uris)),
             Commons.getLinksPath(version, basePath, linkName, Accept.JSON_FULLMETA));
@@ -536,6 +540,19 @@ public abstract class AbstractUtilities {
           }
         }
         sequence.put(entitySetName, Integer.valueOf(res));
+      } else if ("Person".equals(entitySetName)) {
+        try {
+          final Map<String, InputStream> value =
+                  getPropertyValues(entity, Collections.<String>singletonList("PersonId"));
+          res = value.isEmpty() ? null : IOUtils.toString(value.values().iterator().next());
+        } catch (Exception e) {
+          if (sequence.containsKey(entitySetName)) {
+            res = String.valueOf(sequence.get(entitySetName) + 1);
+          } else {
+            throw new Exception(String.format("Unable to retrieve entity key value for %s", entitySetName));
+          }
+        }
+        sequence.put(entitySetName, Integer.valueOf(res));
       } else if ("ComputerDetail".equals(entitySetName)) {
         try {
           final Map<String, InputStream> value =
@@ -713,6 +730,7 @@ public abstract class AbstractUtilities {
           final Accept accept,
           final String ifMatch)
           throws Exception {
+
     final Map.Entry<String, InputStream> entityInfo = readEntity(entitySetName, entityId, accept);
 
     final String etag = Commons.getETag(entityInfo.getKey(), version);
@@ -791,6 +809,9 @@ public abstract class AbstractUtilities {
   public abstract InputStream addEditLink(
           final InputStream content, final String title, final String href) throws Exception;
 
+  public abstract InputStream addOperation(
+          final InputStream content, final String name, final String metaAnchor, final String href) throws Exception;
+
   protected abstract InputStream replaceProperty(
           final InputStream src, final InputStream replacement, final List<String> path, final boolean justValue)
           throws Exception;
@@ -808,4 +829,4 @@ public abstract class AbstractUtilities {
 
   public abstract Map.Entry<String, List<String>> extractLinkURIs(
           final String entitySetName, final String entityId, final String linkName) throws Exception;
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/bb748ec0/fit/src/main/java/org/apache/olingo/fit/utils/AbstractXMLUtilities.java
----------------------------------------------------------------------
diff --git a/fit/src/main/java/org/apache/olingo/fit/utils/AbstractXMLUtilities.java b/fit/src/main/java/org/apache/olingo/fit/utils/AbstractXMLUtilities.java
index 88349a9..831ae42 100644
--- a/fit/src/main/java/org/apache/olingo/fit/utils/AbstractXMLUtilities.java
+++ b/fit/src/main/java/org/apache/olingo/fit/utils/AbstractXMLUtilities.java
@@ -192,7 +192,7 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
       while (true) {
         final Map.Entry<Integer, XmlElement> linkInfo =
                 extractElement(reader, null,
-                Collections.<String>singletonList(Constants.get(version, ConstantKey.LINK)), startDepth, 2, 2);
+                        Collections.<String>singletonList(Constants.get(version, ConstantKey.LINK)), startDepth, 2, 2);
 
         startDepth = linkInfo.getKey();
 
@@ -243,7 +243,7 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
         try {
           final XmlElement inlineElement =
                   extractElement(link.getContentReader(), null,
-                  Collections.<String>singletonList(Constants.get(version, ConstantKey.INLINE)), 0, -1, -1).
+                          Collections.<String>singletonList(Constants.get(version, ConstantKey.INLINE)), 0, -1, -1).
                   getValue();
           final XMLEventReader inlineReader = inlineElement.getContentReader();
 
@@ -434,7 +434,6 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
 
     final ByteArrayOutputStream copy = new ByteArrayOutputStream();
     IOUtils.copy(content, copy);
-
     IOUtils.closeQuietly(content);
 
     XMLEventReader reader = getEventReader(new ByteArrayInputStream(copy.toByteArray()));
@@ -448,7 +447,7 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
       // check edit link existence
       extractElement(reader, writer, Collections.<String>singletonList(Constants.get(version, ConstantKey.LINK)),
               Collections.<Map.Entry<String, String>>singletonList(
-              new AbstractMap.SimpleEntry<String, String>("rel", "edit")), false, 0, -1, -1);
+                      new AbstractMap.SimpleEntry<String, String>("rel", "edit")), false, 0, -1, -1);
 
       addAtomElement(IOUtils.toInputStream(editLinkElement), writer);
       writer.add(reader);
@@ -481,6 +480,21 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
     return new ByteArrayInputStream(bos.toByteArray());
   }
 
+  @Override
+  public InputStream addOperation(final InputStream content, final String name, final String metaAnchor,
+          final String href) throws Exception {
+
+    final ByteArrayOutputStream copy = new ByteArrayOutputStream();
+    IOUtils.copy(content, copy);
+    IOUtils.closeQuietly(content);
+
+    final String action = String.format("<m:action metadata=\"%s%s\" title=\"%s\" target=\"%s\"/>",
+            Constants.get(version, ConstantKey.DEFAULT_SERVICE_URL), metaAnchor, name, href);
+    final String newContent = new String(copy.toByteArray(), "UTF-8").replaceAll("\\<content ", action + "\\<content ");
+
+    return IOUtils.toInputStream(newContent, "UTF-8");
+  }
+
   public InputStream addAtomContent(
           final InputStream content, final String title, final String href)
           throws Exception {
@@ -526,7 +540,7 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
         try {
           final XmlElement entryElement =
                   extractElement(reader, writer, Collections.<String>singletonList(
-                  Constants.get(version, ConstantKey.PROPERTIES)), 0, 2, 3).getValue();
+                                  Constants.get(version, ConstantKey.PROPERTIES)), 0, 2, 3).getValue();
 
           addAtomElement(
                   IOUtils.toInputStream("<content type=\"application/xml\">"),
@@ -578,7 +592,6 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
     final String skipTokenDirPath = fsManager.getAbsolutePath(basePath + Constants.get(version, ConstantKey.SKIP_TOKEN),
             null);
 
-
     try {
       final FileObject skipToken = fsManager.resolve(skipTokenDirPath);
       final FileObject[] files = fsManager.findByExtension(skipToken, Accept.XML.getExtension().substring(1));
@@ -592,7 +605,6 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
       LOG.debug("Resource path '{}' not found", skipTokenDirPath);
     }
 
-
     return count;
   }
 
@@ -783,7 +795,7 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
       if (event.getEventType() == XMLStreamConstants.START_ELEMENT
               && Constants.get(version, ConstantKey.LINK).equals(event.asStartElement().getName().getLocalPart())
               && !fieldToBeSaved.contains(
-              event.asStartElement().getAttributeByName(new QName("title")).getValue())
+                      event.asStartElement().getAttributeByName(new QName("title")).getValue())
               && !"edit".equals(event.asStartElement().getAttributeByName(new QName("rel")).getValue())) {
         writeCurrent = false;
       } else if (event.getEventType() == XMLStreamConstants.END_ELEMENT
@@ -791,13 +803,13 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
         writeNext = true;
       } else if (event.getEventType() == XMLStreamConstants.START_ELEMENT
               && (Constants.get(version, ConstantKey.PROPERTIES)).equals(
-              event.asStartElement().getName().getLocalPart())) {
+                      event.asStartElement().getName().getLocalPart())) {
         writeCurrent = true;
         writeNext = false;
         inProperties = true;
       } else if (event.getEventType() == XMLStreamConstants.END_ELEMENT
               && (Constants.get(version, ConstantKey.PROPERTIES)).equals(
-              event.asEndElement().getName().getLocalPart())) {
+                      event.asEndElement().getName().getLocalPart())) {
         writeCurrent = true;
       } else if (inProperties) {
         if (event.getEventType() == XMLStreamConstants.START_ELEMENT) {
@@ -814,7 +826,7 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
         } else if (event.getEventType() == XMLStreamConstants.END_ELEMENT
                 && StringUtils.isNotBlank(currentName)
                 && (Constants.get(version, ConstantKey.ATOM_PROPERTY_PREFIX) + currentName.trim()).equals(
-                event.asEndElement().getName().getLocalPart())) {
+                        event.asEndElement().getName().getLocalPart())) {
           writeNext = false;
           currentName = null;
         }
@@ -840,7 +852,6 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
     // if (!found.isEmpty()) {
     //     throw new Exception(String.format("Could not find a properties '%s'", found));
     // }
-
     return new ByteArrayInputStream(bos.toByteArray());
   }
 
@@ -883,10 +894,10 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
 
         final XmlElement entry =
                 extractElement(
-                getEventReader(readEntity(uri.getKey(), uri.getValue(), Accept.ATOM).getValue()),
-                null,
-                Collections.<String>singletonList("entry"),
-                0, 1, 1).getValue();
+                        getEventReader(readEntity(uri.getKey(), uri.getValue(), Accept.ATOM).getValue()),
+                        null,
+                        Collections.<String>singletonList("entry"),
+                        0, 1, 1).getValue();
 
         IOUtils.copy(entry.toStream(), writer, encoding);
       } catch (Exception e) {
@@ -923,7 +934,7 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
 
     final Map.Entry<Integer, XmlElement> propertyElement =
             extractElement(reader, null,
-            Collections.<String>singletonList(Constants.get(version, ConstantKey.PROPERTIES)), 0, 2, 3);
+                    Collections.<String>singletonList(Constants.get(version, ConstantKey.PROPERTIES)), 0, 2, 3);
     reader.close();
 
     reader = propertyElement.getValue().getContentReader();
@@ -947,7 +958,7 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
       while (true) {
         final Map.Entry<Integer, XmlElement> linkElement =
                 extractElement(reader, null,
-                Collections.<String>singletonList(Constants.get(version, ConstantKey.LINK)), pos, 2, 2);
+                        Collections.<String>singletonList(Constants.get(version, ConstantKey.LINK)), pos, 2, 2);
 
         res.put("[Constants.get(version, ConstantKey.LINK)]"
                 + linkElement.getValue().getStart().getAttributeByName(new QName("title")).getValue(),
@@ -977,7 +988,7 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
     // ---------------------------------
     Map.Entry<Integer, XmlElement> propertyElement =
             extractElement(reader, writer,
-            Collections.<String>singletonList(Constants.get(version, ConstantKey.PROPERTIES)), 0, 2, 3);
+                    Collections.<String>singletonList(Constants.get(version, ConstantKey.PROPERTIES)), 0, 2, 3);
 
     writer.flush();
 
@@ -1030,7 +1041,6 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
     // ---------------------------------
     // add navigationm changes
     // ---------------------------------
-
     // remove existent links
     for (Map.Entry<String, InputStream> remains : properties.entrySet()) {
 
@@ -1111,9 +1121,9 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
     try {
       final XmlElement linkElement =
               extractElement(reader, writer,
-              Collections.<String>singletonList(Constants.get(version, ConstantKey.LINK)),
-              Collections.<Map.Entry<String, String>>singletonList(new SimpleEntry<String, String>("title", linkName)),
-              false, 0, -1, -1).getValue();
+                      Collections.<String>singletonList(Constants.get(version, ConstantKey.LINK)),
+                      Collections.<Map.Entry<String, String>>singletonList(
+                              new SimpleEntry<String, String>("title", linkName)), false, 0, -1, -1).getValue();
       writer.add(linkElement.getStart());
 
       // ------------------------------------------
@@ -1155,7 +1165,6 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
     final Map.Entry<Integer, XmlElement> prop = extractElement(getEventReader(src), null, atomPathElements, 0, 3, 4);
     IOUtils.closeQuietly(src);
 
-
     final Attribute type =
             prop.getValue().getStart().getAttributeByName(new QName(Constants.get(version, ConstantKey.TYPE)));
 
@@ -1216,6 +1225,7 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
   public InputStream getProperty(
           final String entitySetName, final String entityId, final List<String> path, final String edmType)
           throws Exception {
+
     final List<String> pathElements = new ArrayList<String>();
 
     for (String element : path) {
@@ -1224,7 +1234,7 @@ public abstract class AbstractXMLUtilities extends AbstractUtilities {
 
     final InputStream src =
             fsManager.readFile(Commons.getEntityBasePath(entitySetName, entityId)
-            + Constants.get(version, ConstantKey.ENTITY), Accept.XML);
+                    + Constants.get(version, ConstantKey.ENTITY), Accept.XML);
 
     final XMLEventReader reader = getEventReader(src);
     final XmlElement property = extractElement(reader, null, pathElements, 0, 3, 4).getValue();

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/bb748ec0/fit/src/main/java/org/apache/olingo/fit/utils/Commons.java
----------------------------------------------------------------------
diff --git a/fit/src/main/java/org/apache/olingo/fit/utils/Commons.java b/fit/src/main/java/org/apache/olingo/fit/utils/Commons.java
index 09a6db4..89aaf97 100644
--- a/fit/src/main/java/org/apache/olingo/fit/utils/Commons.java
+++ b/fit/src/main/java/org/apache/olingo/fit/utils/Commons.java
@@ -68,6 +68,7 @@ public abstract class Commons {
     sequence.put("ComputerDetail", 1000);
     sequence.put("AllGeoTypesSet", 1000);
     sequence.put("Orders", 1000);
+    sequence.put("Person", 1000);
 
     mediaContent.put("CustomerInfo", "CustomerinfoId");
     mediaContent.put("Car", "VIN");
@@ -113,7 +114,7 @@ public abstract class Commons {
     try {
       return FSManager.instance(version)
               .getAbsolutePath(basePath + Constants.get(version, ConstantKey.LINKS_FILE_PATH)
-              + File.separatorChar + linkName, accept);
+                      + File.separatorChar + linkName, accept);
     } catch (Exception e) {
       throw new IOException(e);
     }
@@ -138,6 +139,7 @@ public abstract class Commons {
 
   public static InputStream getLinksAsATOM(final Map.Entry<String, Collection<String>> link)
           throws IOException {
+
     final StringBuilder builder = new StringBuilder();
     builder.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
     builder.append("<links xmlns=\"http://schemas.microsoft.com/ado/2007/08/dataservices\">");
@@ -160,6 +162,7 @@ public abstract class Commons {
   public static InputStream getLinksAsJSON(
           final String entitySetName, final Map.Entry<String, Collection<String>> link)
           throws IOException {
+
     final ObjectNode links = new ObjectNode(JsonNodeFactory.instance);
     links.put(
             Constants.get(ConstantKey.JSON_ODATAMETADATA_NAME),