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/18 11:05:59 UTC

git commit: Moving contained CRUD tests from external test service to fit

Repository: olingo-odata4
Updated Branches:
  refs/heads/master fe8df96fe -> e4a4f9e6e


Moving contained CRUD tests from external test service to fit


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

Branch: refs/heads/master
Commit: e4a4f9e6eb85ba182084088e67029dc2b833939a
Parents: fe8df96
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Fri Apr 18 11:05:37 2014 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Fri Apr 18 11:05:37 2014 +0200

----------------------------------------------------------------------
 .../org/apache/olingo/fit/AbstractServices.java |  58 ++---
 .../apache/olingo/fit/V3ActionOverloading.java  |   4 +
 .../java/org/apache/olingo/fit/V4OpenType.java  |   5 +-
 .../java/org/apache/olingo/fit/V4Services.java  | 215 +++++++++++++++++--
 .../olingo/fit/utils/AbstractUtilities.java     |   5 +-
 .../org/apache/olingo/fit/utils/Commons.java    |   1 +
 .../org/apache/olingo/fit/utils/FSManager.java  |  15 +-
 .../core/it/v4/EntityCreateTestITCase.java      |  39 +---
 .../core/it/v4/EntityRetrieveTestITCase.java    |  10 +-
 .../edm/primitivetype/EdmDateTimeOffset.java    |  29 ++-
 .../primitivetype/EdmDateTimeOffsetTest.java    |   9 +
 11 files changed, 271 insertions(+), 119 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e4a4f9e6/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 9f992e9..b624ebe 100644
--- a/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java
+++ b/fit/src/main/java/org/apache/olingo/fit/AbstractServices.java
@@ -122,17 +122,27 @@ public abstract class AbstractServices {
 
   protected final ODataServiceVersion version;
 
+  protected final FITAtomDeserializer atomDeserializer;
+
+  protected final AtomSerializer atomSerializer;
+
+  protected final ObjectMapper mapper;
+
+  protected final DataBinder dataBinder;
+
   protected final AbstractXMLUtilities xml;
 
   protected final AbstractJSONUtilities json;
 
-  @Context
-  protected UriInfo uriInfo;
-
   protected Metadata metadata;
 
   public AbstractServices(final ODataServiceVersion version) throws Exception {
     this.version = version;
+    this.atomDeserializer = Commons.getAtomDeserializer(version);
+    this.atomSerializer = Commons.getAtomSerializer(version);
+    this.mapper = Commons.getJsonMapper(version);
+    this.dataBinder = new DataBinder(version);
+
     if (version.compareTo(ODataServiceVersion.V30) <= 0) {
       this.xml = new org.apache.olingo.fit.utils.v3.XMLUtilities();
       this.json = new org.apache.olingo.fit.utils.v3.JSONUtilities();
@@ -453,10 +463,6 @@ public abstract class AbstractServices {
         throw new ConcurrentModificationException("Concurrent modification");
       }
 
-      final ObjectMapper mapper = Commons.getJsonMapper(version);
-      final FITAtomDeserializer atomDeserializer = Commons.getAtomDeserializer(version);
-      final AtomSerializer atomSerializer = Commons.getAtomSerializer(version);
-
       final Accept contentTypeValue = Accept.parse(contentType, version);
 
       final AtomEntryImpl entryChanges;
@@ -471,7 +477,7 @@ public abstract class AbstractServices {
                 mapper.readValue(IOUtils.toInputStream(changes), new TypeReference<JSONEntryImpl>() {
                 });
 
-        entryChanges = (new DataBinder(version)).getAtomEntry(jcont.getObject());
+        entryChanges = dataBinder.getAtomEntry(jcont.getObject());
       }
 
       final Container<AtomEntryImpl> container = atomDeserializer.read(entityInfo.getValue(), AtomEntryImpl.class);
@@ -554,9 +560,6 @@ public abstract class AbstractServices {
                 getUtilities(acceptType).readEntry(acceptType, IOUtils.toInputStream(entity)));
       }
 
-      final FITAtomDeserializer atomDeserializer = Commons.getAtomDeserializer(version);
-      final ObjectMapper mapper = Commons.getJsonMapper(version);
-
       final Container<AtomEntryImpl> cres;
       if (acceptType == Accept.ATOM) {
         cres = atomDeserializer.read(res, AtomEntryImpl.class);
@@ -564,7 +567,7 @@ public abstract class AbstractServices {
         final Container<JSONEntryImpl> jcont = mapper.readValue(res, new TypeReference<JSONEntryImpl>() {
         });
         cres = new Container<AtomEntryImpl>(jcont.getContextURL(), jcont.getMetadataETag(),
-                (new DataBinder(version)).getAtomEntry(jcont.getObject()));
+                dataBinder.getAtomEntry(jcont.getObject()));
       }
 
       final String path = Commons.getEntityBasePath(entitySetName, entityId);
@@ -615,17 +618,12 @@ public abstract class AbstractServices {
     AbstractUtilities utils = xml;
     try {
       final Accept acceptType = Accept.parse(accept, version);
-
       if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
         throw new UnsupportedMediaTypeException("Unsupported media type");
       }
 
       utils = getUtilities(acceptType);
 
-      final FITAtomDeserializer atomDeserializer = Commons.getAtomDeserializer(version);
-      final AtomSerializer atomSerializer = Commons.getAtomSerializer(version);
-      final ObjectMapper mapper = Commons.getJsonMapper(version);
-
       final Container<AtomEntryImpl> container;
 
       final EntitySet entitySet = getMetadataObj().getEntitySet(entitySetName);
@@ -670,7 +668,7 @@ public abstract class AbstractServices {
                   mapper.readValue(IOUtils.toInputStream(entity), new TypeReference<JSONEntryImpl>() {
                   });
 
-          entry = new DataBinder(version).getAtomEntry(jcontainer.getObject());
+          entry = dataBinder.getAtomEntry(jcontainer.getObject());
 
           container = new Container<AtomEntryImpl>(
                   jcontainer.getContextURL(),
@@ -933,8 +931,6 @@ public abstract class AbstractServices {
 
         final InputStream feed = FSManager.instance(version).readFile(builder.toString(), Accept.ATOM);
 
-        final FITAtomDeserializer atomDeserializer = Commons.getAtomDeserializer(version);
-        final AtomSerializer atomSerializer = Commons.getAtomSerializer(version);
         final Container<AtomFeedImpl> container = atomDeserializer.read(feed, AtomFeedImpl.class);
 
         setInlineCount(container.getObject(), count);
@@ -947,11 +943,9 @@ public abstract class AbstractServices {
           writer.flush();
           writer.close();
         } else {
-          final ObjectMapper mapper = Commons.getJsonMapper(version);
-
           mapper.writeValue(
                   writer, new JsonFeedContainer<JSONFeedImpl>(container.getContextURL(), container.getMetadataETag(),
-                          new DataBinder(version).getJsonFeed(container.getObject())));
+                          dataBinder.getJsonFeed(container.getObject())));
         }
 
         return xml.createResponse(
@@ -1058,9 +1052,6 @@ public abstract class AbstractServices {
 
       final InputStream entity = entityInfo.getValue();
 
-      final FITAtomDeserializer atomDeserializer = Commons.getAtomDeserializer(version);
-      final AtomSerializer atomSerializer = Commons.getAtomSerializer(version);
-
       Container<AtomEntryImpl> container = atomDeserializer.read(entity, AtomEntryImpl.class);
       if (container.getContextURL() == null) {
         container = new Container<AtomEntryImpl>(URI.create(Constants.get(version, ConstantKey.DEFAULT_SERVICE_URL)
@@ -1375,7 +1366,7 @@ public abstract class AbstractServices {
 
       final AbstractUtilities utils = getUtilities(null);
 
-      InputStream res = utils.putMediaInMemory(entitySetName, entityId, IOUtils.toInputStream(value));
+      final InputStream res = utils.putMediaInMemory(entitySetName, entityId, IOUtils.toInputStream(value));
 
       final String location = uriInfo.getRequestUri().toASCIIString().replace("/$value", "");
 
@@ -1551,10 +1542,9 @@ public abstract class AbstractServices {
         }
 
         try {
-          LinkInfo linkInfo = xml.readLinks(entitySetName, entityId, path, Accept.XML);
+          final LinkInfo linkInfo = xml.readLinks(entitySetName, entityId, path, Accept.XML);
           final Map.Entry<String, List<String>> links = xml.extractLinkURIs(linkInfo.getLinks());
-          InputStream stream = xml.readEntities(links.getValue(), path, links.getKey(), linkInfo.isFeed());
-          final FITAtomDeserializer atomDeserializer = Commons.getAtomDeserializer(version);
+          final InputStream stream = xml.readEntities(links.getValue(), path, links.getKey(), linkInfo.isFeed());
 
           final ByteArrayOutputStream content = new ByteArrayOutputStream();
           final OutputStreamWriter writer = new OutputStreamWriter(content, Constants.ENCODING);
@@ -1563,32 +1553,28 @@ public abstract class AbstractServices {
             final Container<Feed> container = atomDeserializer.<Feed, AtomFeedImpl>read(stream, AtomFeedImpl.class);
 
             if (acceptType == Accept.ATOM) {
-              final AtomSerializer atomSerializer = Commons.getAtomSerializer(version);
               atomSerializer.write(writer, container);
               writer.flush();
               writer.close();
             } else {
-              final ObjectMapper mapper = Commons.getJsonMapper(version);
               mapper.writeValue(
                       writer,
                       new JsonFeedContainer<JSONFeedImpl>(container.getContextURL(),
                               container.getMetadataETag(),
-                              (new DataBinder(version)).getJsonFeed((AtomFeedImpl) container.getObject())));
+                              dataBinder.getJsonFeed((AtomFeedImpl) container.getObject())));
             }
           } else {
             final Container<Entry> container = atomDeserializer.<Entry, AtomEntryImpl>read(stream, AtomEntryImpl.class);
             if (acceptType == Accept.ATOM) {
-              final AtomSerializer atomSerializer = Commons.getAtomSerializer(version);
               atomSerializer.write(writer, container);
               writer.flush();
               writer.close();
             } else {
-              final ObjectMapper mapper = Commons.getJsonMapper(version);
               mapper.writeValue(
                       writer,
                       new JsonEntryContainer<JSONEntryImpl>(container.getContextURL(),
                               container.getMetadataETag(),
-                              (new DataBinder(version)).getJsonEntry((AtomEntryImpl) container.getObject())));
+                              dataBinder.getJsonEntry((AtomEntryImpl) container.getObject())));
             }
           }
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e4a4f9e6/fit/src/main/java/org/apache/olingo/fit/V3ActionOverloading.java
----------------------------------------------------------------------
diff --git a/fit/src/main/java/org/apache/olingo/fit/V3ActionOverloading.java b/fit/src/main/java/org/apache/olingo/fit/V3ActionOverloading.java
index d26daa5..a070527 100644
--- a/fit/src/main/java/org/apache/olingo/fit/V3ActionOverloading.java
+++ b/fit/src/main/java/org/apache/olingo/fit/V3ActionOverloading.java
@@ -30,9 +30,11 @@ 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 static javax.ws.rs.core.Response.status;
+import javax.ws.rs.core.UriInfo;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.olingo.commons.api.data.Feed;
@@ -117,6 +119,7 @@ public class V3ActionOverloading extends AbstractServices {
   @GET
   @Path("/Product({entityId})")
   public Response getProduct(
+          @Context UriInfo uriInfo,
           @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
           @PathParam("entityId") final String entityId,
           @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {
@@ -158,6 +161,7 @@ public class V3ActionOverloading extends AbstractServices {
   @GET
   @Path("/OrderLine(OrderId={orderId},ProductId={productId})")
   public Response getOrderLine(
+          @Context UriInfo uriInfo,
           @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
           @PathParam("orderId") final String orderId,
           @PathParam("productId") final String productId,

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e4a4f9e6/fit/src/main/java/org/apache/olingo/fit/V4OpenType.java
----------------------------------------------------------------------
diff --git a/fit/src/main/java/org/apache/olingo/fit/V4OpenType.java b/fit/src/main/java/org/apache/olingo/fit/V4OpenType.java
index aff2f43..748a614 100644
--- a/fit/src/main/java/org/apache/olingo/fit/V4OpenType.java
+++ b/fit/src/main/java/org/apache/olingo/fit/V4OpenType.java
@@ -56,7 +56,7 @@ public class V4OpenType {
   public V4OpenType() throws Exception {
     this.openMetadata = new Metadata(FSManager.instance(ODataServiceVersion.V40).
             readFile("openType" + StringUtils.capitalize(Constants.get(ODataServiceVersion.V40, ConstantKey.METADATA)),
-            Accept.XML));
+                    Accept.XML));
     this.services = new V4Services() {
       @Override
       protected Metadata getMetadataObj() {
@@ -115,8 +115,7 @@ public class V4OpenType {
           @QueryParam("$expand") @DefaultValue(StringUtils.EMPTY) String expand,
           @QueryParam("$select") @DefaultValue(StringUtils.EMPTY) String select) {
 
-    return replaceServiceName(
-            services.getEntityInternal(
+    return replaceServiceName(services.getEntityInternal(
             uriInfo.getRequestUri().toASCIIString(), accept, entitySetName, entityId, format, expand, select, false));
   }
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e4a4f9e6/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 8de437b..c6de7fe 100644
--- a/fit/src/main/java/org/apache/olingo/fit/V4Services.java
+++ b/fit/src/main/java/org/apache/olingo/fit/V4Services.java
@@ -20,16 +20,22 @@ package org.apache.olingo.fit;
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
 import java.util.Map;
+import javax.ws.rs.DELETE;
 import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
 import javax.ws.rs.HeaderParam;
 import javax.ws.rs.NotFoundException;
-import org.apache.olingo.fit.utils.XHTTPMethodInterceptor;
+import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 import org.apache.commons.codec.binary.Base64;
@@ -41,10 +47,14 @@ import org.apache.olingo.commons.api.data.Feed;
 import org.apache.olingo.commons.api.data.Property;
 import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
 import org.apache.olingo.commons.core.data.AtomEntryImpl;
+import org.apache.olingo.commons.core.data.AtomFeedImpl;
+import org.apache.olingo.commons.core.data.AtomSerializer;
 import org.apache.olingo.commons.core.data.JSONEntryImpl;
+import org.apache.olingo.commons.core.data.JSONFeedImpl;
 import org.apache.olingo.commons.core.edm.EdmTypeInfo;
 import org.apache.olingo.fit.methods.PATCH;
 import org.apache.olingo.fit.serializer.FITAtomDeserializer;
+import org.apache.olingo.fit.serializer.JsonFeedContainer;
 import org.apache.olingo.fit.utils.AbstractUtilities;
 import org.apache.olingo.fit.utils.Accept;
 import org.apache.olingo.fit.utils.Commons;
@@ -54,6 +64,7 @@ import org.apache.olingo.fit.utils.DataBinder;
 import org.apache.olingo.fit.utils.FSManager;
 import org.apache.olingo.fit.utils.LinkInfo;
 import org.apache.olingo.fit.utils.ResolvingReferencesInterceptor;
+import org.apache.olingo.fit.utils.XHTTPMethodInterceptor;
 import org.springframework.stereotype.Service;
 
 @Service
@@ -98,7 +109,7 @@ public class V4Services extends AbstractServices {
 
       return utils.getValue().createResponse(
               FSManager.instance(version).readFile(Constants.get(version, ConstantKey.REF)
-              + File.separatorChar + filename, utils.getKey()),
+                      + File.separatorChar + filename, utils.getKey()),
               null,
               utils.getKey());
     } catch (Exception e) {
@@ -120,7 +131,7 @@ public class V4Services extends AbstractServices {
 
     final Response response =
             getEntityInternal(uriInfo.getRequestUri().toASCIIString(),
-            accept, entitySetName, entityId, accept, StringUtils.EMPTY, StringUtils.EMPTY, false);
+                    accept, entitySetName, entityId, accept, StringUtils.EMPTY, StringUtils.EMPTY, false);
     return response.getStatus() >= 400
             ? postNewEntity(uriInfo, accept, contentType, prefer, entitySetName, changes)
             : super.patchEntity(uriInfo, accept, contentType, prefer, ifMatch, entitySetName, entityId, changes);
@@ -144,12 +155,18 @@ public class V4Services extends AbstractServices {
       return postNewEntity(uriInfo, accept, contentType, prefer, entitySetName, entityId);
     }
   }
+  
+  private StringBuilder containedPath(final String entityId, final String containedEntitySetName) {
+    return new StringBuilder("Accounts").append(File.separatorChar).
+            append(entityId).append(File.separatorChar).
+            append("links").append(File.separatorChar).
+            append(containedEntitySetName);
+  }
 
   @GET
-  @Path("/{entitySetName}({entityId})/{containedEntitySetName}({containedEntityId})")
+  @Path("/Accounts({entityId})/{containedEntitySetName}({containedEntityId})")
   public Response getContainedEntity(
           @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
-          @PathParam("entitySetName") String entitySetName,
           @PathParam("entityId") String entityId,
           @PathParam("containedEntitySetName") String containedEntitySetName,
           @PathParam("containedEntityId") String containedEntityId,
@@ -162,23 +179,106 @@ public class V4Services extends AbstractServices {
       } else {
         acceptType = Accept.parse(accept, version);
       }
-
       if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
         throw new UnsupportedMediaTypeException("Unsupported media type");
       }
 
-      final LinkInfo links = xml.readLinks(
-              entitySetName, entityId, containedEntitySetName + "(" + containedEntityId + ")", acceptType);
+      final InputStream entry = FSManager.instance(version).
+              readFile(containedPath(entityId, containedEntitySetName).
+                      append('(').append(containedEntityId).append(')').toString(), Accept.ATOM);
+
+      final Container<AtomEntryImpl> container = atomDeserializer.read(entry, AtomEntryImpl.class);
 
       return xml.createResponse(
-              links.getLinks(),
-              links.getEtag(),
+              null,
+              xml.writeEntry(acceptType, container),
+              null,
               acceptType);
     } catch (Exception e) {
       return xml.createFaultResponse(accept, e);
     }
   }
 
+  @POST
+  @Path("/Accounts({entityId})/{containedEntitySetName:.*}")
+  public Response postContainedEntity(
+          @Context UriInfo uriInfo,
+          @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
+          @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) String contentType,
+          @PathParam("entityId") String entityId,
+          @PathParam("containedEntitySetName") String containedEntitySetName,
+          final String entity) {
+
+    // default
+    try {
+      final Accept acceptType = Accept.parse(accept, version);
+      if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
+        throw new UnsupportedMediaTypeException("Unsupported media type");
+      }
+
+      final AbstractUtilities utils = getUtilities(acceptType);
+
+      // 1. parse the entry (from Atom or JSON) into AtomEntryImpl
+      final Container<AtomEntryImpl> entryContainer;
+      final AtomEntryImpl entry;
+      final Accept contentTypeValue = Accept.parse(contentType, version);
+      if (Accept.ATOM == contentTypeValue) {
+        entryContainer = atomDeserializer.read(IOUtils.toInputStream(entity), AtomEntryImpl.class);
+        entry = entryContainer.getObject();
+      } else {
+        final Container<JSONEntryImpl> jcontainer =
+                mapper.readValue(IOUtils.toInputStream(entity), new TypeReference<JSONEntryImpl>() {
+                });
+
+        entry = dataBinder.getAtomEntry(jcontainer.getObject());
+
+        entryContainer = new Container<AtomEntryImpl>(
+                jcontainer.getContextURL(),
+                jcontainer.getMetadataETag(),
+                entry);
+      }
+
+      final EdmTypeInfo contained = new EdmTypeInfo.Builder().setTypeExpression(getMetadataObj().
+              getNavigationProperties("Accounts").get(containedEntitySetName).getType()).build();
+      final String entityKey = getUtilities(contentTypeValue).
+              getDefaultEntryKey(contained.getFullQualifiedName().getName(), entry);
+
+      // 2. Store the new entity
+      final String atomEntryRelativePath = containedPath(entityId, containedEntitySetName).
+              append('(').append(entityKey).append(')').toString();
+      FSManager.instance(version).putInMemory(
+              utils.writeEntry(Accept.ATOM, entryContainer),
+              FSManager.instance(version).getAbsolutePath(atomEntryRelativePath, Accept.ATOM));
+
+      // 3. Update the contained entity set
+      final String atomFeedRelativePath = containedPath(entityId, containedEntitySetName).toString();
+      final InputStream feedIS = FSManager.instance(version).readFile(atomFeedRelativePath, Accept.ATOM);
+      final Container<AtomFeedImpl> feedContainer = atomDeserializer.read(feedIS, AtomFeedImpl.class);
+      feedContainer.getObject().getEntries().add(entry);
+
+      final ByteArrayOutputStream content = new ByteArrayOutputStream();
+      final OutputStreamWriter writer = new OutputStreamWriter(content, Constants.ENCODING);
+      atomSerializer.write(writer, feedContainer);
+      writer.flush();
+      writer.close();
+
+      FSManager.instance(version).putInMemory(
+              new ByteArrayInputStream(content.toByteArray()),
+              FSManager.instance(version).getAbsolutePath(atomFeedRelativePath, Accept.ATOM));
+
+      // Finally, return
+      return utils.createResponse(
+              uriInfo.getRequestUri().toASCIIString() + "(" + entityKey + ")",
+              utils.writeEntry(acceptType, entryContainer),
+              null,
+              acceptType,
+              Response.Status.CREATED);
+    } catch (Exception e) {
+      LOG.error("While creating new contained entity", e);
+      return xml.createFaultResponse(accept, e);
+    }
+  }
+
   @PATCH
   @Path("/{entitySetName}({entityId})/{containedEntitySetName}({containedEntityId})")
   public Response patchContainedEntity(
@@ -211,7 +311,6 @@ public class V4Services extends AbstractServices {
       final LinkInfo links = xml.readLinks(
               entitySetName, entityId, containedEntitySetName + "(" + containedEntityId + ")", Accept.ATOM);
 
-      final FITAtomDeserializer atomDeserializer = Commons.getAtomDeserializer(version);
       Container<AtomEntryImpl> container = atomDeserializer.read(links.getLinks(), AtomEntryImpl.class);
       final AtomEntryImpl original = container.getObject();
 
@@ -225,12 +324,9 @@ public class V4Services extends AbstractServices {
                 getNavigationProperty(containedEntitySetName).getType();
         final EdmTypeInfo typeInfo = new EdmTypeInfo.Builder().setTypeExpression(containedType).build();
 
-        final ObjectMapper mapper = Commons.getJsonMapper(version);
-        final DataBinder dataBinder = new DataBinder(version);
-
         final Container<JSONEntryImpl> jsonContainer = mapper.readValue(IOUtils.toInputStream(changes),
                 new TypeReference<JSONEntryImpl>() {
-        });
+                });
         jsonContainer.getObject().setType(typeInfo.getFullQualifiedName().toString());
         entryChanges = dataBinder.getAtomEntry(jsonContainer.getObject());
       }
@@ -251,4 +347,93 @@ public class V4Services extends AbstractServices {
       return xml.createFaultResponse(accept, e);
     }
   }
+
+  @DELETE
+  @Path("/Accounts({entityId})/{containedEntitySetName}({containedEntityId})")
+  public Response removeContainedEntity(
+          @PathParam("entityId") String entityId,
+          @PathParam("containedEntitySetName") String containedEntitySetName,
+          @PathParam("containedEntityId") String containedEntityId) {
+
+    try {
+      // 1. Fetch the contained entity to be removed
+      final InputStream entry = FSManager.instance(version).
+              readFile(containedPath(entityId, containedEntitySetName).
+                      append('(').append(containedEntityId).append(')').toString(), Accept.ATOM);
+      final Container<AtomEntryImpl> container = atomDeserializer.read(entry, AtomEntryImpl.class);
+      
+      // 2. Remove the contained entity
+      final String atomEntryRelativePath = containedPath(entityId, containedEntitySetName).
+              append('(').append(containedEntityId).append(')').toString();
+      FSManager.instance(version).deleteFile(atomEntryRelativePath);
+
+      // 3. Update the contained entity set
+      final String atomFeedRelativePath = containedPath(entityId, containedEntitySetName).toString();
+      final InputStream feedIS = FSManager.instance(version).readFile(atomFeedRelativePath, Accept.ATOM);
+      final Container<AtomFeedImpl> feedContainer = atomDeserializer.read(feedIS, AtomFeedImpl.class);     
+      feedContainer.getObject().getEntries().remove(container.getObject());
+
+      final ByteArrayOutputStream content = new ByteArrayOutputStream();
+      final OutputStreamWriter writer = new OutputStreamWriter(content, Constants.ENCODING);
+      atomSerializer.write(writer, feedContainer);
+      writer.flush();
+      writer.close();
+
+      FSManager.instance(version).putInMemory(
+              new ByteArrayInputStream(content.toByteArray()),
+              FSManager.instance(version).getAbsolutePath(atomFeedRelativePath, Accept.ATOM));
+
+      return xml.createResponse(null, null, null, null, Response.Status.NO_CONTENT);
+    } catch (Exception e) {
+      return xml.createFaultResponse(Accept.XML.toString(version), e);
+    }
+  }
+
+  @GET
+  @Path("/Accounts({entityId})/{containedEntitySetName:.*}")
+  public Response getContainedEntitySet(
+          @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
+          @PathParam("entityId") String entityId,
+          @PathParam("containedEntitySetName") String containedEntitySetName,
+          @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) String format) {
+
+    try {
+      final Accept acceptType;
+      if (StringUtils.isNotBlank(format)) {
+        acceptType = Accept.valueOf(format.toUpperCase());
+      } else {
+        acceptType = Accept.parse(accept, version);
+      }
+      if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
+        throw new UnsupportedMediaTypeException("Unsupported media type");
+      }
+
+      final InputStream feed = FSManager.instance(version).
+              readFile(containedPath(entityId, containedEntitySetName).toString(), Accept.ATOM);
+
+      final Container<AtomFeedImpl> container = atomDeserializer.read(feed, AtomFeedImpl.class);
+
+      final ByteArrayOutputStream content = new ByteArrayOutputStream();
+      final OutputStreamWriter writer = new OutputStreamWriter(content, Constants.ENCODING);
+
+      if (acceptType == Accept.ATOM) {
+        atomSerializer.write(writer, container);
+        writer.flush();
+        writer.close();
+      } else {
+        mapper.writeValue(
+                writer, new JsonFeedContainer<JSONFeedImpl>(container.getContextURL(), container.getMetadataETag(),
+                        dataBinder.getJsonFeed(container.getObject())));
+      }
+
+      return xml.createResponse(
+              null,
+              new ByteArrayInputStream(content.toByteArray()),
+              null,
+              acceptType);
+    } catch (Exception e) {
+      return xml.createFaultResponse(accept, e);
+    }
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e4a4f9e6/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 22a7f5b..a84ef60 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,7 +120,6 @@ public abstract class AbstractUtilities {
    * Retrieve entity links and inlines.
    *
    * @param entitySetName
-   * @param entityKey
    * @param is
    * @return
    * @throws IOException
@@ -589,7 +588,7 @@ public abstract class AbstractUtilities {
       final ObjectMapper mapper = Commons.getJsonMapper(version);
       mapper.writeValue(
               writer, new JsonEntryContainer<JSONEntryImpl>(container.getContextURL(), container.getMetadataETag(),
-                      (new DataBinder(version)).getJsonEntry((AtomEntryImpl) container.getObject())));
+                      new DataBinder(version).getJsonEntry((AtomEntryImpl) container.getObject())));
     }
 
     return new ByteArrayInputStream(content.toByteArray());
@@ -672,6 +671,8 @@ public abstract class AbstractUtilities {
         }
         Commons.SEQUENCE.put(entitySetName, productDetailId);
         Commons.SEQUENCE.put("Products", productId);
+      } else if ("PaymentInstrument".equals(entitySetName)) {
+        res = getDefaultEntryKey(entitySetName, entry, "PaymentInstrumentID");
       } else {
         throw new Exception(String.format("EntitySet '%s' not found", entitySetName));
       }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e4a4f9e6/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 2844676..c3c0cbb 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
@@ -89,6 +89,7 @@ public abstract class Commons {
     SEQUENCE.put("RowIndex", 1000);
     SEQUENCE.put("Products", 1000);
     SEQUENCE.put("ProductDetails", 1000);
+    SEQUENCE.put("PaymentInstrument", 10192);
 
     MEDIA_CONTENT.put("CustomerInfo", "CustomerinfoId");
     MEDIA_CONTENT.put("Car", "VIN");

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e4a4f9e6/fit/src/main/java/org/apache/olingo/fit/utils/FSManager.java
----------------------------------------------------------------------
diff --git a/fit/src/main/java/org/apache/olingo/fit/utils/FSManager.java b/fit/src/main/java/org/apache/olingo/fit/utils/FSManager.java
index 46654fe..b7d21f4 100644
--- a/fit/src/main/java/org/apache/olingo/fit/utils/FSManager.java
+++ b/fit/src/main/java/org/apache/olingo/fit/utils/FSManager.java
@@ -102,10 +102,7 @@ public class FSManager {
   public void putInMemory(final Container<AtomEntryImpl> container, final String relativePath)
           throws IOException {
     try {
-      final ODataServiceVersion serviceVersion =
-              version == ODataServiceVersion.V30 ? ODataServiceVersion.V30 : ODataServiceVersion.V40;
-
-      final AtomSerializer atomSerializer = Commons.getAtomSerializer(serviceVersion);
+      final AtomSerializer atomSerializer = Commons.getAtomSerializer(version);
 
       final ByteArrayOutputStream content = new ByteArrayOutputStream();
       final OutputStreamWriter writer = new OutputStreamWriter(content, Constants.ENCODING);
@@ -116,13 +113,12 @@ public class FSManager {
       putInMemory(new ByteArrayInputStream(content.toByteArray()), getAbsolutePath(relativePath, Accept.ATOM));
       content.reset();
 
-      final ObjectMapper mapper = Commons.getJsonMapper(serviceVersion);
+      final ObjectMapper mapper = Commons.getJsonMapper(version);
       mapper.writeValue(
               writer, new JsonEntryContainer<JSONEntryImpl>(
-              container.getContextURL(),
-              container.getMetadataETag(),
-              (new DataBinder(version == ODataServiceVersion.V30 ? ODataServiceVersion.V30 : ODataServiceVersion.V40)).
-              getJsonEntry(container.getObject())));
+                      container.getContextURL(),
+                      container.getMetadataETag(),
+                      new DataBinder(version).getJsonEntry(container.getObject())));
 
       putInMemory(new ByteArrayInputStream(content.toByteArray()), getAbsolutePath(relativePath, Accept.JSON_FULLMETA));
     } catch (Exception e) {
@@ -164,7 +160,6 @@ public class FSManager {
   }
 
   public void deleteFile(final String relativePath) {
-
     for (Accept accept : Accept.values()) {
       final String path = getAbsolutePath(relativePath, accept);
       LOG.info("Delete {}", path);

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e4a4f9e6/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/EntityCreateTestITCase.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/EntityCreateTestITCase.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/EntityCreateTestITCase.java
index 6a736c6..6101041 100644
--- a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/EntityCreateTestITCase.java
+++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/EntityCreateTestITCase.java
@@ -21,12 +21,8 @@ package org.apache.olingo.client.core.it.v4;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.Socket;
 import java.net.URI;
 import java.util.Calendar;
-import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.RandomUtils;
 import org.apache.olingo.client.api.communication.request.cud.ODataEntityCreateRequest;
 import org.apache.olingo.client.api.communication.response.ODataDeleteResponse;
@@ -43,31 +39,10 @@ import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
 import org.apache.olingo.commons.api.edm.FullQualifiedName;
 import org.apache.olingo.commons.api.format.ODataPubFormat;
 import org.apache.olingo.commons.core.domain.v4.ODataEntityImpl;
-import org.junit.Assume;
-import org.junit.BeforeClass;
 import org.junit.Test;
 
 public class EntityCreateTestITCase extends AbstractTestITCase {
 
-  private static final String serviceRoot = "http://odatae2etest.azurewebsites.net/javatest/DefaultService";
-
-  // TODO: remove once fit provides contained entity CRUD
-  @BeforeClass
-  public static void checkServerIsOnline() throws IOException {
-    final Socket socket = new Socket();
-    boolean reachable = false;
-    try {
-      socket.connect(new InetSocketAddress("odatae2etest.azurewebsites.net", 80), 2000);
-      reachable = true;
-    } catch (Exception e) {
-      LOG.warn("External test service not reachable, ignoring this whole class: {}",
-              OperationImportInvokeTestITCase.class.getName());
-    } finally {
-      IOUtils.closeQuietly(socket);
-    }
-    Assume.assumeTrue(reachable);
-  }
-
   private void order(final ODataPubFormat format, final int id) {
     final ODataEntity order = new ODataEntityImpl(
             new FullQualifiedName("Microsoft.Test.OData.Services.ODataWCFService.Order"));
@@ -116,8 +91,8 @@ public class EntityCreateTestITCase extends AbstractTestITCase {
   }
 
   private void onContained(final ODataPubFormat format) {
-    final URI uri = getClient().getURIBuilder(serviceRoot).appendEntitySetSegment("Accounts").appendKeySegment(101).
-            appendNavigationSegment("MyPaymentInstruments").build();
+    final URI uri = getClient().getURIBuilder(testStaticServiceRootURL).appendEntitySetSegment("Accounts").
+            appendKeySegment(101).appendNavigationSegment("MyPaymentInstruments").build();
 
     // 1. read contained collection before any operation
     ODataEntitySet instruments = getClient().getRetrieveRequestFactory().getEntitySetRequest(uri).execute().getBody();
@@ -171,8 +146,9 @@ public class EntityCreateTestITCase extends AbstractTestITCase {
     onContained(ODataPubFormat.JSON);
   }
 
-  private void deepInsert(final ODataPubFormat format) throws EdmPrimitiveTypeException {
-    final int productId = RandomUtils.nextInt(10, 20);
+  private void deepInsert(final ODataPubFormat format, final int productId, final int productDetailId)
+          throws EdmPrimitiveTypeException {
+
     final ODataEntity product = getClient().getObjectFactory().
             newEntity(new FullQualifiedName("Microsoft.Test.OData.Services.ODataWCFService.Product"));
     product.getProperties().add(getClient().getObjectFactory().newPrimitiveProperty("ProductID",
@@ -201,7 +177,6 @@ public class EntityCreateTestITCase extends AbstractTestITCase {
     product.getProperty("CoverColors").getCollectionValue().add(getClient().getObjectFactory().
             newEnumValue("Microsoft.Test.OData.Services.ODataWCFService.Color", "Red"));
 
-    final int productDetailId = RandomUtils.nextInt(10, 20);
     final ODataEntity detail = getClient().getObjectFactory().
             newEntity(new FullQualifiedName("Microsoft.Test.OData.Services.ODataWCFService.ProductDetail"));
     detail.getProperties().add(getClient().getObjectFactory().newPrimitiveProperty("ProductID",
@@ -242,11 +217,11 @@ public class EntityCreateTestITCase extends AbstractTestITCase {
 
   @Test
   public void atomDeepInsert() throws EdmPrimitiveTypeException {
-    deepInsert(ODataPubFormat.ATOM);
+    deepInsert(ODataPubFormat.ATOM, 10, 10);
   }
 
   @Test
   public void jsonDeepInsert() throws EdmPrimitiveTypeException {
-    deepInsert(ODataPubFormat.JSON_FULL_METADATA);
+    deepInsert(ODataPubFormat.JSON_FULL_METADATA, 11, 11);
   }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e4a4f9e6/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/EntityRetrieveTestITCase.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/EntityRetrieveTestITCase.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/EntityRetrieveTestITCase.java
index 2917bc4..9e0b390 100644
--- a/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/EntityRetrieveTestITCase.java
+++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/it/v4/EntityRetrieveTestITCase.java
@@ -248,16 +248,16 @@ public class EntityRetrieveTestITCase extends AbstractTestITCase {
   }
 
   @Test
-  public void retrieveEntityViaReferenceAsAtom() {
-    retrieveEntityViaReference(ODataPubFormat.ATOM);
+  public void atomReference() {
+    reference(ODataPubFormat.ATOM);
   }
 
   @Test
-  public void retrieveEntityViaReferenceAsJSON() {
-    retrieveEntityViaReference(ODataPubFormat.JSON_FULL_METADATA);
+  public void jsonReference() {
+    reference(ODataPubFormat.JSON_FULL_METADATA);
   }
 
-  private void retrieveEntityViaReference(final ODataPubFormat format) {
+  private void reference(final ODataPubFormat format) {
     final URIBuilder uriBuilder = client.getURIBuilder(getServiceRoot()).
             appendEntitySetSegment("Orders").appendKeySegment(8).appendNavigationSegment("CustomerForOrder").
             appendRefSegment();

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e4a4f9e6/lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/primitivetype/EdmDateTimeOffset.java
----------------------------------------------------------------------
diff --git a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/primitivetype/EdmDateTimeOffset.java b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/primitivetype/EdmDateTimeOffset.java
index 44dc72d..3f1d27a 100644
--- a/lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/primitivetype/EdmDateTimeOffset.java
+++ b/lib/commons-core/src/main/java/org/apache/olingo/commons/core/edm/primitivetype/EdmDateTimeOffset.java
@@ -292,25 +292,22 @@ public final class EdmDateTimeOffset extends SingletonPrimitiveType {
           final Integer precision) throws IllegalArgumentException {
 
     if (fractionalSeconds > 0) {
-      if (precision == null || precision < String.valueOf(fractionalSeconds).length()) {
-        throw new IllegalArgumentException();
-      }
-
-      // Keep output similar to Calendar's, if possible            
-      String fractionals = NANO_FORMAT.get().format(fractionalSeconds);
-      boolean canStart = false;
-      int firstZeroAfterNonZeroIdx = -1;
-      for (int i = 0; i < fractionals.length() && firstZeroAfterNonZeroIdx == -1; i++) {
-        if ('0' != fractionals.charAt(i)) {
-          canStart = true;
-        } else if (canStart && '0' == fractionals.charAt(i)) {
-          firstZeroAfterNonZeroIdx = i;
+      String formatted = NANO_FORMAT.get().format(fractionalSeconds);
+      int actualLength = formatted.length();
+      boolean nonZeroFound = false;
+      for (int i = formatted.length() - 1; i >= 0 && !nonZeroFound; i--) {
+        if ('0' == formatted.charAt(i)) {
+          actualLength--;
+        } else {
+          nonZeroFound = true;
         }
       }
-      if (firstZeroAfterNonZeroIdx != -1 && 0 == Integer.valueOf(fractionals.substring(firstZeroAfterNonZeroIdx))) {
-        fractionals = fractionals.substring(0, firstZeroAfterNonZeroIdx);
+
+      if (precision == null || precision < actualLength) {
+        throw new IllegalArgumentException();
       }
-      result.append('.').append(fractionals);
+
+      result.append('.').append(formatted.substring(0, actualLength));
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e4a4f9e6/lib/commons-core/src/test/java/org/apache/olingo/commons/core/edm/primitivetype/EdmDateTimeOffsetTest.java
----------------------------------------------------------------------
diff --git a/lib/commons-core/src/test/java/org/apache/olingo/commons/core/edm/primitivetype/EdmDateTimeOffsetTest.java b/lib/commons-core/src/test/java/org/apache/olingo/commons/core/edm/primitivetype/EdmDateTimeOffsetTest.java
index 4e49a62..e03eff7 100644
--- a/lib/commons-core/src/test/java/org/apache/olingo/commons/core/edm/primitivetype/EdmDateTimeOffsetTest.java
+++ b/lib/commons-core/src/test/java/org/apache/olingo/commons/core/edm/primitivetype/EdmDateTimeOffsetTest.java
@@ -62,6 +62,15 @@ public class EdmDateTimeOffsetTest extends PrimitiveTypeBaseTest {
     dateTime.setTimeZone(TimeZone.getTimeZone("GMT+11:00"));
     assertEquals("2012-02-29T01:02:03+11:00", instance.valueToString(dateTime, null, null, null, null, null));
 
+    dateTime.set(Calendar.MILLISECOND, 503);
+    assertEquals("2012-02-29T01:02:03.503+11:00", instance.valueToString(dateTime, null, null, 3, null, null));    
+    
+    dateTime.set(Calendar.MILLISECOND, 530);
+    assertEquals("2012-02-29T01:02:03.53+11:00", instance.valueToString(dateTime, null, null, 3, null, null));    
+
+    dateTime.set(Calendar.MILLISECOND, 53);
+    assertEquals("2012-02-29T01:02:03.053+11:00", instance.valueToString(dateTime, null, null, 3, null, null));    
+
     final Long millis = 1330558323007L;
     assertEquals("2012-02-29T23:32:03.007Z", instance.valueToString(millis, null, null, 3, null, null));
     assertEquals("1969-12-31T23:59:59.9Z", instance.valueToString(-100L, null, null, 1, null, null));