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/05/27 11:00:45 UTC

[3/3] git commit: [OLINGO-260] Adding option to work non-transactionally (e.g. without batch)

[OLINGO-260] Adding option to work non-transactionally (e.g. without batch)


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

Branch: refs/heads/master
Commit: a66bd98b59ace7789d4107601f0be50be3f8e20c
Parents: 0774803
Author: Francesco Chicchiriccò <--global>
Authored: Tue May 27 11:00:32 2014 +0200
Committer: Francesco Chicchiriccò <--global>
Committed: Tue May 27 11:00:32 2014 +0200

----------------------------------------------------------------------
 .../ext/proxy/EntityContainerFactory.java       |  73 ++-
 .../commons/AbstractInvocationHandler.java      |   2 +-
 .../commons/AbstractPersistenceManager.java     | 451 +++++++++++++++
 .../AbstractStructuredInvocationHandler.java    |   2 +-
 .../EntityContainerInvocationHandler.java       |   2 +-
 .../NonTransactionalPersistenceManagerImpl.java |  60 ++
 .../ext/proxy/commons/PersistenceChanges.java   |  37 ++
 .../proxy/commons/PersistenceManagerImpl.java   | 568 -------------------
 .../ext/proxy/commons/TransactionItems.java     |  81 +++
 .../TransactionalPersistenceManagerImpl.java    | 108 ++++
 .../java/org/apache/olingo/fit/V4Services.java  |  57 +-
 .../olingo/fit/proxy/v4/AbstractTestITCase.java |   1 +
 .../fit/proxy/v4/EntityCreateTestITCase.java    |   2 +-
 .../fit/proxy/v4/EntityUpdateTestITCase.java    |  95 +++-
 .../fit/proxy/v4/MediaEntityTestITCase.java     |  51 +-
 ...TransactionalAuthEntityCreateTestITCase.java |  53 ++
 .../NonTransactionalEntityCreateTestITCase.java |  50 ++
 .../NonTransactionalEntityUpdateTestITCase.java |  49 ++
 .../NonTransactionalMediaEntityTestITCase.java  |  50 ++
 19 files changed, 1159 insertions(+), 633 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/a66bd98b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/EntityContainerFactory.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/EntityContainerFactory.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/EntityContainerFactory.java
index b2ce911..c36aa77 100644
--- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/EntityContainerFactory.java
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/EntityContainerFactory.java
@@ -24,7 +24,10 @@ import java.util.concurrent.ConcurrentHashMap;
 import org.apache.olingo.client.api.CommonEdmEnabledODataClient;
 import org.apache.olingo.client.core.ODataClientFactory;
 import org.apache.olingo.commons.api.format.ODataPubFormat;
+import org.apache.olingo.ext.proxy.api.PersistenceManager;
 import org.apache.olingo.ext.proxy.commons.EntityContainerInvocationHandler;
+import org.apache.olingo.ext.proxy.commons.NonTransactionalPersistenceManagerImpl;
+import org.apache.olingo.ext.proxy.commons.TransactionalPersistenceManagerImpl;
 import org.apache.olingo.ext.proxy.context.Context;
 
 /**
@@ -40,35 +43,80 @@ public final class EntityContainerFactory<C extends CommonEdmEnabledODataClient<
   private final Map<Class<?>, Object> ENTITY_CONTAINERS = new ConcurrentHashMap<Class<?>, Object>();
 
   @SuppressWarnings("unchecked")
-  private static <C extends CommonEdmEnabledODataClient<?>> EntityContainerFactory<C> getInstance(final C client) {
+  private static <C extends CommonEdmEnabledODataClient<?>> EntityContainerFactory<C> getInstance(
+          final C client, final boolean transactional) {
+
     if (!FACTORY_PER_SERVICEROOT.containsKey(client.getServiceRoot())) {
       client.getConfiguration().setDefaultPubFormat(ODataPubFormat.JSON_FULL_METADATA);
-      final EntityContainerFactory<C> instance = new EntityContainerFactory<C>(client);
+      final EntityContainerFactory<C> instance = new EntityContainerFactory<C>(client, transactional);
       FACTORY_PER_SERVICEROOT.put(client.getServiceRoot(), instance);
     }
 
     return (EntityContainerFactory<C>) FACTORY_PER_SERVICEROOT.get(client.getServiceRoot());
   }
 
+  /**
+   * Gives an OData 3.0 instance for given service root, operating in transactions (with batch requests).
+   *
+   * @param serviceRoot OData service root
+   * @return OData 3.0 instance for given service root, operating in transactions (with batch requests)
+   */
   public static EntityContainerFactory<org.apache.olingo.client.api.v3.EdmEnabledODataClient> getV3(
           final String serviceRoot) {
 
-    return getInstance(ODataClientFactory.getEdmEnabledV3(serviceRoot));
+    return getV3(serviceRoot, true);
+  }
+
+  /**
+   * Gives an OData 3.0 instance for given service root.
+   *
+   * @param serviceRoot OData service root
+   * @param transactional whether operating in transactions (with batch requests) or not
+   * @return OData 3.0 instance for given service root
+   */
+  public static EntityContainerFactory<org.apache.olingo.client.api.v3.EdmEnabledODataClient> getV3(
+          final String serviceRoot, final boolean transactional) {
+
+    return getInstance(ODataClientFactory.getEdmEnabledV3(serviceRoot), transactional);
   }
 
+  /**
+   * Gives an OData 4.0 instance for given service root, operating in transactions (with batch requests).
+   *
+   * @param serviceRoot OData service root
+   * @return OData 4.0 instance for given service root, operating in transactions (with batch requests)
+   */
   public static EntityContainerFactory<org.apache.olingo.client.api.v4.EdmEnabledODataClient> getV4(
           final String serviceRoot) {
 
-    return getInstance(ODataClientFactory.getEdmEnabledV4(serviceRoot));
+    return getV4(serviceRoot, true);
+  }
+
+  /**
+   * Gives an OData 4.0 instance for given service root.
+   *
+   * @param serviceRoot OData service root
+   * @param transactional whether operating in transactions (with batch requests) or not
+   * @return OData 4.0 instance for given service root
+   */
+  public static EntityContainerFactory<org.apache.olingo.client.api.v4.EdmEnabledODataClient> getV4(
+          final String serviceRoot, final boolean transactional) {
+
+    return getInstance(ODataClientFactory.getEdmEnabledV4(serviceRoot), transactional);
   }
 
   private final CommonEdmEnabledODataClient<?> client;
 
   private final Context context;
 
-  private EntityContainerFactory(final CommonEdmEnabledODataClient<?> client) {
+  private final boolean transactional;
+
+  private PersistenceManager persistenceManager;
+
+  private EntityContainerFactory(final CommonEdmEnabledODataClient<?> client, final boolean transactional) {
     this.client = client;
     this.context = new Context();
+    this.transactional = transactional;
   }
 
   @SuppressWarnings("unchecked")
@@ -80,6 +128,21 @@ public final class EntityContainerFactory<C extends CommonEdmEnabledODataClient<
     return context;
   }
 
+  public boolean isTransactional() {
+    return transactional;
+  }
+
+  public PersistenceManager getPersistenceManager() {
+    synchronized (this) {
+      if (persistenceManager == null) {
+        persistenceManager = transactional
+                ? new TransactionalPersistenceManagerImpl(this)
+                : new NonTransactionalPersistenceManagerImpl(this);
+      }
+    }
+    return persistenceManager;
+  }
+
   /**
    * Return an initialized concrete implementation of the passed EntityContainer interface.
    *

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/a66bd98b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractInvocationHandler.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractInvocationHandler.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractInvocationHandler.java
index 2293c39..1728a7a 100644
--- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractInvocationHandler.java
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractInvocationHandler.java
@@ -210,7 +210,7 @@ abstract class AbstractInvocationHandler implements InvocationHandler {
 
     // 2. IMPORTANT: flush any pending change *before* invoke if this operation is side effecting
     if (annotation.type() == OperationType.ACTION) {
-      new PersistenceManagerImpl(containerHandler.getFactory()).flush();
+      containerHandler.getFactory().getPersistenceManager().flush();
     }
 
     // 3. invoke

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/a66bd98b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractPersistenceManager.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractPersistenceManager.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractPersistenceManager.java
new file mode 100644
index 0000000..67369ef
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractPersistenceManager.java
@@ -0,0 +1,451 @@
+/*
+ * 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.ext.proxy.commons;
+
+import java.io.InputStream;
+import java.lang.reflect.Proxy;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.olingo.client.api.communication.header.ODataPreferences;
+import org.apache.olingo.client.api.communication.request.cud.ODataDeleteRequest;
+import org.apache.olingo.client.api.communication.request.cud.ODataEntityUpdateRequest;
+import org.apache.olingo.client.api.communication.request.streamed.ODataMediaEntityUpdateRequest;
+import org.apache.olingo.client.api.communication.request.streamed.ODataStreamUpdateRequest;
+import org.apache.olingo.client.core.uri.URIUtils;
+import org.apache.olingo.commons.api.domain.CommonODataEntity;
+import org.apache.olingo.commons.api.domain.ODataLink;
+import org.apache.olingo.commons.api.domain.ODataLinkType;
+import org.apache.olingo.commons.api.domain.v4.ODataEntity;
+import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
+import org.apache.olingo.commons.api.format.ODataMediaFormat;
+import org.apache.olingo.ext.proxy.EntityContainerFactory;
+import org.apache.olingo.ext.proxy.api.PersistenceManager;
+import org.apache.olingo.ext.proxy.api.annotations.NavigationProperty;
+import org.apache.olingo.ext.proxy.context.AttachedEntity;
+import org.apache.olingo.ext.proxy.context.AttachedEntityStatus;
+import org.apache.olingo.ext.proxy.context.EntityLinkDesc;
+import org.apache.olingo.ext.proxy.utils.CoreUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+abstract class AbstractPersistenceManager implements PersistenceManager {
+
+  /**
+   * Logger.
+   */
+  protected static final Logger LOG = LoggerFactory.getLogger(AbstractPersistenceManager.class);
+
+  private static final long serialVersionUID = 2065240290461241515L;
+
+  protected final EntityContainerFactory<?> factory;
+
+  AbstractPersistenceManager(final EntityContainerFactory<?> factory) {
+    this.factory = factory;
+  }
+
+  protected abstract void doFlush(final PersistenceChanges changes, final TransactionItems items);
+
+  @Override
+  public void flush() {
+    final PersistenceChanges changes = new PersistenceChanges();
+    final TransactionItems items = new TransactionItems();
+
+    int pos = 0;
+    final List<EntityLinkDesc> delayedUpdates = new ArrayList<EntityLinkDesc>();
+    for (AttachedEntity attachedEntity : factory.getContext().entityContext()) {
+      final AttachedEntityStatus status = attachedEntity.getStatus();
+      if (((status != AttachedEntityStatus.ATTACHED
+              && status != AttachedEntityStatus.LINKED) || attachedEntity.getEntity().isChanged())
+              && !items.contains(attachedEntity.getEntity())) {
+
+        pos++;
+        pos = processEntityContext(attachedEntity.getEntity(), pos, items, delayedUpdates, changes);
+      }
+    }
+
+    processDelayedUpdates(delayedUpdates, pos, items, changes);
+
+    doFlush(changes, items);
+
+    factory.getContext().detachAll();
+  }
+
+  private ODataLink buildNavigationLink(final String name, final URI uri, final ODataLinkType type) {
+    ODataLink result;
+
+    switch (type) {
+      case ENTITY_NAVIGATION:
+        result = factory.getClient().getObjectFactory().newEntityNavigationLink(name, uri);
+        break;
+
+      case ENTITY_SET_NAVIGATION:
+        result = factory.getClient().getObjectFactory().newEntitySetNavigationLink(name, uri);
+        break;
+
+      default:
+        throw new IllegalArgumentException("Invalid link type " + type.name());
+    }
+
+    return result;
+  }
+
+  protected int processEntityContext(
+          final EntityInvocationHandler handler,
+          int pos,
+          final TransactionItems items,
+          final List<EntityLinkDesc> delayedUpdates,
+          final PersistenceChanges changeset) {
+
+    LOG.debug("Process '{}'", handler);
+
+    items.put(handler, null);
+
+    final CommonODataEntity entity = handler.getEntity();
+    entity.getNavigationLinks().clear();
+
+    final AttachedEntityStatus currentStatus = factory.getContext().entityContext().getStatus(handler);
+
+    if (AttachedEntityStatus.DELETED != currentStatus) {
+      entity.getProperties().clear();
+      CoreUtils.addProperties(factory.getClient(), handler.getPropertyChanges(), entity);
+
+      if (entity instanceof ODataEntity) {
+        ((ODataEntity) entity).getAnnotations().clear();
+        CoreUtils.addAnnotations(factory.getClient(), handler.getAnnotations(), (ODataEntity) entity);
+
+        for (Map.Entry<String, AnnotatableInvocationHandler> entry : handler.getPropAnnotatableHandlers().entrySet()) {
+          CoreUtils.addAnnotations(factory.getClient(),
+                  entry.getValue().getAnnotations(), ((ODataEntity) entity).getProperty(entry.getKey()));
+        }
+      }
+    }
+
+    for (Map.Entry<NavigationProperty, Object> property : handler.getLinkChanges().entrySet()) {
+      final ODataLinkType type = Collection.class.isAssignableFrom(property.getValue().getClass())
+              ? ODataLinkType.ENTITY_SET_NAVIGATION
+              : ODataLinkType.ENTITY_NAVIGATION;
+
+      final Set<EntityInvocationHandler> toBeLinked = new HashSet<EntityInvocationHandler>();
+      for (Object proxy : type == ODataLinkType.ENTITY_SET_NAVIGATION
+              ? (Collection) property.getValue() : Collections.singleton(property.getValue())) {
+
+        final EntityInvocationHandler target = (EntityInvocationHandler) Proxy.getInvocationHandler(proxy);
+
+        final AttachedEntityStatus status = factory.getContext().entityContext().getStatus(target);
+
+        final URI editLink = target.getEntity().getEditLink();
+
+        if ((status == AttachedEntityStatus.ATTACHED || status == AttachedEntityStatus.LINKED) && !target.isChanged()) {
+          entity.addLink(buildNavigationLink(
+                  property.getKey().name(),
+                  URIUtils.getURI(factory.getClient().getServiceRoot(), editLink.toASCIIString()), type));
+        } else {
+          if (!items.contains(target)) {
+            pos = processEntityContext(target, pos, items, delayedUpdates, changeset);
+            pos++;
+          }
+
+          final Integer targetPos = items.get(target);
+          if (targetPos == null) {
+            // schedule update for the current object
+            LOG.debug("Schedule '{}' from '{}' to '{}'", type.name(), handler, target);
+            toBeLinked.add(target);
+          } else if (status == AttachedEntityStatus.CHANGED) {
+            entity.addLink(buildNavigationLink(
+                    property.getKey().name(),
+                    URIUtils.getURI(factory.getClient().getServiceRoot(), editLink.toASCIIString()), type));
+          } else {
+            // create the link for the current object
+            LOG.debug("'{}' from '{}' to (${}) '{}'", type.name(), handler, targetPos, target);
+
+            entity.addLink(buildNavigationLink(property.getKey().name(), URI.create("$" + targetPos), type));
+          }
+        }
+      }
+
+      if (!toBeLinked.isEmpty()) {
+        delayedUpdates.add(new EntityLinkDesc(property.getKey().name(), handler, toBeLinked, type));
+      }
+    }
+
+    if (entity instanceof ODataEntity) {
+      for (Map.Entry<String, AnnotatableInvocationHandler> entry
+              : handler.getNavPropAnnotatableHandlers().entrySet()) {
+
+        CoreUtils.addAnnotations(factory.getClient(),
+                entry.getValue().getAnnotations(),
+                (org.apache.olingo.commons.api.domain.v4.ODataLink) entity.getNavigationLink(entry.getKey()));
+      }
+    }
+
+    // insert into the process queue
+    LOG.debug("{}: Insert '{}' into the process queue", pos, handler);
+    final AttachedEntityStatus processedStatus = queue(handler, entity, changeset);
+
+    items.put(handler, pos);
+
+    if (processedStatus != AttachedEntityStatus.DELETED) {
+      int startingPos = pos;
+
+      if (handler.getEntity().isMediaEntity() && handler.isChanged()) {
+        // update media properties
+        if (!handler.getPropertyChanges().isEmpty()) {
+          final URI targetURI = currentStatus == AttachedEntityStatus.NEW
+                  ? URI.create("$" + startingPos)
+                  : URIUtils.getURI(
+                          factory.getClient().getServiceRoot(), handler.getEntity().getEditLink().toASCIIString());
+          queueUpdate(handler, targetURI, entity, changeset);
+          pos++;
+          items.put(handler, pos);
+        }
+
+        // update media content
+        if (handler.getStreamChanges() != null) {
+          final URI targetURI = currentStatus == AttachedEntityStatus.NEW
+                  ? URI.create("$" + startingPos + "/$value")
+                  : URIUtils.getURI(
+                          factory.getClient().getServiceRoot(),
+                          handler.getEntity().getEditLink().toASCIIString() + "/$value");
+
+          queueUpdateMediaEntity(handler, targetURI, handler.getStreamChanges(), changeset);
+
+          // update media info (use null key)
+          pos++;
+          items.put(null, pos);
+        }
+      }
+
+      for (Map.Entry<String, InputStream> streamedChanges : handler.getStreamedPropertyChanges().entrySet()) {
+        final URI targetURI = currentStatus == AttachedEntityStatus.NEW
+                ? URI.create("$" + startingPos) : URIUtils.getURI(
+                        factory.getClient().getServiceRoot(),
+                        CoreUtils.getMediaEditLink(streamedChanges.getKey(), entity).toASCIIString());
+
+        queueUpdateMediaResource(handler, targetURI, streamedChanges.getValue(), changeset);
+
+        // update media info (use null key)
+        pos++;
+        items.put(handler, pos);
+      }
+    }
+
+    return pos;
+  }
+
+  protected void processDelayedUpdates(
+          final List<EntityLinkDesc> delayedUpdates,
+          int pos,
+          final TransactionItems items,
+          final PersistenceChanges changeset) {
+
+    for (EntityLinkDesc delayedUpdate : delayedUpdates) {
+      pos++;
+      items.put(delayedUpdate.getSource(), pos);
+
+      final CommonODataEntity changes =
+              factory.getClient().getObjectFactory().newEntity(delayedUpdate.getSource().getEntity().getTypeName());
+
+      AttachedEntityStatus status = factory.getContext().entityContext().getStatus(delayedUpdate.getSource());
+
+      final URI sourceURI;
+      if (status == AttachedEntityStatus.CHANGED) {
+        sourceURI = URIUtils.getURI(
+                factory.getClient().getServiceRoot(),
+                delayedUpdate.getSource().getEntity().getEditLink().toASCIIString());
+      } else {
+        int sourcePos = items.get(delayedUpdate.getSource());
+        sourceURI = URI.create("$" + sourcePos);
+      }
+
+      for (EntityInvocationHandler target : delayedUpdate.getTargets()) {
+        status = factory.getContext().entityContext().getStatus(target);
+
+        final URI targetURI;
+        if (status == AttachedEntityStatus.CHANGED) {
+          targetURI = URIUtils.getURI(
+                  factory.getClient().getServiceRoot(), target.getEntity().getEditLink().toASCIIString());
+        } else {
+          int targetPos = items.get(target);
+          targetURI = URI.create("$" + targetPos);
+        }
+
+        changes.addLink(delayedUpdate.getType() == ODataLinkType.ENTITY_NAVIGATION
+                ? factory.getClient().getObjectFactory().
+                newEntityNavigationLink(delayedUpdate.getSourceName(), targetURI)
+                : factory.getClient().getObjectFactory().
+                newEntitySetNavigationLink(delayedUpdate.getSourceName(), targetURI));
+
+        LOG.debug("'{}' from {} to {}", delayedUpdate.getType().name(), sourceURI, targetURI);
+      }
+
+      queueUpdate(delayedUpdate.getSource(), sourceURI, changes, changeset);
+    }
+  }
+
+  private AttachedEntityStatus queue(
+          final EntityInvocationHandler handler,
+          final CommonODataEntity entity,
+          final PersistenceChanges changeset) {
+
+    switch (factory.getContext().entityContext().getStatus(handler)) {
+      case NEW:
+        queueCreate(handler, entity, changeset);
+        return AttachedEntityStatus.NEW;
+
+      case CHANGED:
+        queueUpdate(handler, entity, changeset);
+        return AttachedEntityStatus.CHANGED;
+
+      case DELETED:
+        queueDelete(handler, entity, changeset);
+        return AttachedEntityStatus.DELETED;
+
+      default:
+        if (handler.isChanged()) {
+          queueUpdate(handler, entity, changeset);
+        }
+        return AttachedEntityStatus.CHANGED;
+    }
+  }
+
+  private void queueCreate(
+          final EntityInvocationHandler handler,
+          final CommonODataEntity entity,
+          final PersistenceChanges changeset) {
+
+    LOG.debug("Create '{}'", handler);
+
+    changeset.addChange(factory.getClient().getCUDRequestFactory().
+            getEntityCreateRequest(handler.getEntitySetURI(), entity), handler);
+  }
+
+  private void queueUpdateMediaEntity(
+          final EntityInvocationHandler handler,
+          final URI uri,
+          final InputStream input,
+          final PersistenceChanges changeset) {
+
+    LOG.debug("Update media entity '{}'", uri);
+
+    final ODataMediaEntityUpdateRequest<?> req =
+            factory.getClient().getCUDRequestFactory().getMediaEntityUpdateRequest(uri, input);
+
+    req.setContentType(StringUtils.isBlank(handler.getEntity().getMediaContentType())
+            ? ODataMediaFormat.WILDCARD.toString()
+            : ODataMediaFormat.fromFormat(handler.getEntity().getMediaContentType()).toString());
+
+    if (StringUtils.isNotBlank(handler.getETag())) {
+      req.setIfMatch(handler.getETag());
+    }
+
+    changeset.addChange(req, handler);
+  }
+
+  private void queueUpdateMediaResource(
+          final EntityInvocationHandler handler,
+          final URI uri,
+          final InputStream input,
+          final PersistenceChanges changeset) {
+
+    LOG.debug("Update media entity '{}'", uri);
+
+    final ODataStreamUpdateRequest req = factory.getClient().getCUDRequestFactory().getStreamUpdateRequest(uri, input);
+
+    if (StringUtils.isNotBlank(handler.getETag())) {
+      req.setIfMatch(handler.getETag());
+    }
+
+    changeset.addChange(req, handler);
+  }
+
+  private void queueUpdate(
+          final EntityInvocationHandler handler,
+          final CommonODataEntity changes,
+          final PersistenceChanges changeset) {
+
+    LOG.debug("Update '{}'", handler.getEntityURI());
+
+    final ODataEntityUpdateRequest<CommonODataEntity> req =
+            factory.getClient().getServiceVersion().compareTo(ODataServiceVersion.V30) <= 0
+            ? ((org.apache.olingo.client.api.v3.EdmEnabledODataClient) factory.getClient()).getCUDRequestFactory().
+            getEntityUpdateRequest(handler.getEntityURI(),
+                    org.apache.olingo.client.api.communication.request.cud.v3.UpdateType.PATCH, changes)
+            : ((org.apache.olingo.client.api.v4.EdmEnabledODataClient) factory.getClient()).getCUDRequestFactory().
+            getEntityUpdateRequest(handler.getEntityURI(),
+                    org.apache.olingo.client.api.communication.request.cud.v4.UpdateType.PATCH, changes);
+
+    req.setPrefer(new ODataPreferences(factory.getClient().getServiceVersion()).returnContent());
+
+    if (StringUtils.isNotBlank(handler.getETag())) {
+      req.setIfMatch(handler.getETag());
+    }
+
+    changeset.addChange(req, handler);
+  }
+
+  private void queueUpdate(
+          final EntityInvocationHandler handler,
+          final URI uri,
+          final CommonODataEntity changes,
+          final PersistenceChanges changeset) {
+
+    LOG.debug("Update '{}'", uri);
+
+    final ODataEntityUpdateRequest<CommonODataEntity> req =
+            factory.getClient().getServiceVersion().compareTo(ODataServiceVersion.V30) <= 0
+            ? ((org.apache.olingo.client.api.v3.EdmEnabledODataClient) factory.getClient()).getCUDRequestFactory().
+            getEntityUpdateRequest(uri,
+                    org.apache.olingo.client.api.communication.request.cud.v3.UpdateType.PATCH, changes)
+            : ((org.apache.olingo.client.api.v4.EdmEnabledODataClient) factory.getClient()).getCUDRequestFactory().
+            getEntityUpdateRequest(uri,
+                    org.apache.olingo.client.api.communication.request.cud.v4.UpdateType.PATCH, changes);
+
+    req.setPrefer(new ODataPreferences(factory.getClient().getServiceVersion()).returnContent());
+
+    if (StringUtils.isNotBlank(handler.getETag())) {
+      req.setIfMatch(handler.getETag());
+    }
+
+    changeset.addChange(req, handler);
+  }
+
+  private void queueDelete(
+          final EntityInvocationHandler handler,
+          final CommonODataEntity entity,
+          final PersistenceChanges changeset) {
+
+    final URI deleteURI = handler.getEntityURI() == null ? entity.getEditLink() : handler.getEntityURI();
+    LOG.debug("Delete '{}'", deleteURI);
+
+    final ODataDeleteRequest req = factory.getClient().getCUDRequestFactory().getDeleteRequest(deleteURI);
+
+    if (StringUtils.isNotBlank(handler.getETag())) {
+      req.setIfMatch(handler.getETag());
+    }
+
+    changeset.addChange(req, handler);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/a66bd98b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractStructuredInvocationHandler.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractStructuredInvocationHandler.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractStructuredInvocationHandler.java
index 9b54380..8b290f2 100644
--- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractStructuredInvocationHandler.java
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractStructuredInvocationHandler.java
@@ -268,7 +268,7 @@ public abstract class AbstractStructuredInvocationHandler extends AbstractInvoca
         final ODataRetrieveResponse<CommonODataEntity> res = req.execute();
 
         navPropValue = getEntityProxy(
-                uri,
+                null,
                 res.getBody(),
                 property.targetContainer(),
                 getClient().newURIBuilder().appendEntitySetSegment(property.targetEntitySet()).build(),

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/a66bd98b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityContainerInvocationHandler.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityContainerInvocationHandler.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityContainerInvocationHandler.java
index 5f9433b..56bcc6d 100644
--- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityContainerInvocationHandler.java
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityContainerInvocationHandler.java
@@ -81,7 +81,7 @@ public final class EntityContainerInvocationHandler extends AbstractInvocationHa
     if (isSelfMethod(method, args)) {
       return invokeSelfMethod(method, args);
     } else if ("flush".equals(method.getName()) && ArrayUtils.isEmpty(args)) {
-      new PersistenceManagerImpl(factory).flush();
+      factory.getPersistenceManager().flush();
       return ClassUtils.returnVoid();
     } else if ("operations".equals(method.getName()) && ArrayUtils.isEmpty(args)) {
       final Class<?> returnType = method.getReturnType();

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/a66bd98b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/NonTransactionalPersistenceManagerImpl.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/NonTransactionalPersistenceManagerImpl.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/NonTransactionalPersistenceManagerImpl.java
new file mode 100644
index 0000000..dedd338
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/NonTransactionalPersistenceManagerImpl.java
@@ -0,0 +1,60 @@
+/*
+ * 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.ext.proxy.commons;
+
+import java.util.Map;
+import org.apache.olingo.client.api.communication.request.ODataBasicRequest;
+import org.apache.olingo.client.api.communication.request.ODataBatchableRequest;
+import org.apache.olingo.client.api.communication.response.ODataEntityCreateResponse;
+import org.apache.olingo.client.api.communication.response.ODataEntityUpdateResponse;
+import org.apache.olingo.client.api.communication.response.ODataResponse;
+import org.apache.olingo.ext.proxy.EntityContainerFactory;
+
+/**
+ * {@link org.apache.olingo.ext.proxy.api.PersistenceManager} implementation not using OData batch requests: any
+ * read-write operation will be sent separately to the OData service when calling <tt>flush()</tt>; any intermediate
+ * error will be logged and ignored.
+ */
+public class NonTransactionalPersistenceManagerImpl extends AbstractPersistenceManager {
+
+  private static final long serialVersionUID = 5082907388513308752L;
+
+  public NonTransactionalPersistenceManagerImpl(final EntityContainerFactory<?> factory) {
+    super(factory);
+  }
+
+  @Override
+  protected void doFlush(final PersistenceChanges changes, final TransactionItems items) {
+    for (Map.Entry<ODataBatchableRequest, EntityInvocationHandler> entry : changes.getChanges().entrySet()) {
+      try {
+        final ODataResponse response = ((ODataBasicRequest<?, ?>) entry.getKey()).execute();
+
+        if (response instanceof ODataEntityCreateResponse && response.getStatusCode() == 201) {
+          entry.getValue().setEntity(((ODataEntityCreateResponse) response).getBody());
+          LOG.debug("Upgrade created object '{}'", entry.getValue());
+        } else if (response instanceof ODataEntityUpdateResponse && response.getStatusCode() == 200) {
+          entry.getValue().setEntity(((ODataEntityUpdateResponse) response).getBody());
+          LOG.debug("Upgrade updated object '{}'", entry.getValue());
+        }
+      } catch (Exception e) {
+        LOG.error("While performing {}", entry.getKey().getURI(), e);
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/a66bd98b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/PersistenceChanges.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/PersistenceChanges.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/PersistenceChanges.java
new file mode 100644
index 0000000..159d36b
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/PersistenceChanges.java
@@ -0,0 +1,37 @@
+/*
+ * 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.ext.proxy.commons;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.apache.olingo.client.api.communication.request.ODataBatchableRequest;
+
+public class PersistenceChanges {
+
+  private final Map<ODataBatchableRequest, EntityInvocationHandler> requests =
+          new LinkedHashMap<ODataBatchableRequest, EntityInvocationHandler>();
+
+  public void addChange(final ODataBatchableRequest request, final EntityInvocationHandler handler) {
+    this.requests.put(request, handler);
+  }
+
+  public Map<ODataBatchableRequest, EntityInvocationHandler> getChanges() {
+    return requests;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/a66bd98b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/PersistenceManagerImpl.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/PersistenceManagerImpl.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/PersistenceManagerImpl.java
deleted file mode 100644
index e7ffc57..0000000
--- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/PersistenceManagerImpl.java
+++ /dev/null
@@ -1,568 +0,0 @@
-/*
- * 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.ext.proxy.commons;
-
-import java.io.InputStream;
-import java.lang.reflect.Proxy;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.olingo.client.api.communication.header.ODataPreferences;
-import org.apache.olingo.client.api.communication.request.ODataRequest;
-import org.apache.olingo.client.api.communication.request.ODataStreamedRequest;
-import org.apache.olingo.client.api.communication.request.batch.BatchManager;
-import org.apache.olingo.client.api.communication.request.batch.CommonODataBatchRequest;
-import org.apache.olingo.client.api.communication.request.batch.ODataBatchResponseItem;
-import org.apache.olingo.client.api.communication.request.batch.ODataChangeset;
-import org.apache.olingo.client.api.communication.request.cud.ODataDeleteRequest;
-import org.apache.olingo.client.api.communication.request.cud.ODataEntityUpdateRequest;
-import org.apache.olingo.client.api.communication.request.streamed.ODataMediaEntityUpdateRequest;
-import org.apache.olingo.client.api.communication.request.streamed.ODataStreamUpdateRequest;
-import org.apache.olingo.client.api.communication.response.ODataBatchResponse;
-import org.apache.olingo.client.api.communication.response.ODataEntityCreateResponse;
-import org.apache.olingo.client.api.communication.response.ODataEntityUpdateResponse;
-import org.apache.olingo.client.api.communication.response.ODataResponse;
-import org.apache.olingo.client.core.communication.request.batch.ODataChangesetResponseItem;
-import org.apache.olingo.client.core.uri.URIUtils;
-import org.apache.olingo.commons.api.domain.CommonODataEntity;
-import org.apache.olingo.commons.api.domain.ODataLink;
-import org.apache.olingo.commons.api.domain.ODataLinkType;
-import org.apache.olingo.commons.api.domain.v4.ODataEntity;
-import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
-import org.apache.olingo.commons.api.format.ODataMediaFormat;
-import org.apache.olingo.ext.proxy.EntityContainerFactory;
-import org.apache.olingo.ext.proxy.api.PersistenceManager;
-import org.apache.olingo.ext.proxy.api.annotations.NavigationProperty;
-import org.apache.olingo.ext.proxy.context.AttachedEntity;
-import org.apache.olingo.ext.proxy.context.AttachedEntityStatus;
-import org.apache.olingo.ext.proxy.context.EntityLinkDesc;
-import org.apache.olingo.ext.proxy.utils.CoreUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-class PersistenceManagerImpl implements PersistenceManager {
-
-  private static final long serialVersionUID = -3320312269235907501L;
-
-  /**
-   * Logger.
-   */
-  private static final Logger LOG = LoggerFactory.getLogger(PersistenceManagerImpl.class);
-
-  private final EntityContainerFactory<?> factory;
-
-  PersistenceManagerImpl(final EntityContainerFactory<?> factory) {
-    this.factory = factory;
-  }
-
-  /**
-   * Transactional changes commit.
-   */
-  @Override
-  public void flush() {
-    final CommonODataBatchRequest request =
-            factory.getClient().getBatchRequestFactory().getBatchRequest(factory.getClient().getServiceRoot());
-    ((ODataRequest) request).setAccept(factory.getClient().getConfiguration().getDefaultBatchAcceptFormat());
-
-    final BatchManager streamManager = (BatchManager) ((ODataStreamedRequest) request).payloadManager();
-
-    final ODataChangeset changeset = streamManager.addChangeset();
-
-    final TransactionItems items = new TransactionItems();
-    final List<EntityLinkDesc> delayedUpdates = new ArrayList<EntityLinkDesc>();
-
-    int pos = 0;
-
-    for (AttachedEntity attachedEntity : factory.getContext().entityContext()) {
-      final AttachedEntityStatus status = attachedEntity.getStatus();
-      if (((status != AttachedEntityStatus.ATTACHED
-              && status != AttachedEntityStatus.LINKED) || attachedEntity.getEntity().isChanged())
-              && !items.contains(attachedEntity.getEntity())) {
-        pos++;
-        pos = processEntityContext(attachedEntity.getEntity(), pos, items, delayedUpdates, changeset);
-      }
-    }
-
-    processDelayedUpdates(delayedUpdates, pos, items, changeset);
-
-    final ODataBatchResponse response = streamManager.getResponse();
-
-    // This should be 202 for service version <= 3.0 and 200 for service version >= 4.0 but it seems that
-    // many service implementations are not fully compliant with this respect.
-    if (response.getStatusCode() != 202 && response.getStatusCode() != 200) {
-      throw new IllegalStateException("Operation failed");
-    }
-
-    if (!items.isEmpty()) {
-      final Iterator<ODataBatchResponseItem> iter = response.getBody();
-      if (!iter.hasNext()) {
-        throw new IllegalStateException("Unexpected operation result");
-      }
-
-      final ODataBatchResponseItem item = iter.next();
-      if (!(item instanceof ODataChangesetResponseItem)) {
-        throw new IllegalStateException("Unexpected batch response item " + item.getClass().getSimpleName());
-      }
-
-      final ODataChangesetResponseItem chgres = (ODataChangesetResponseItem) item;
-
-      for (Integer changesetItemId : items.sortedValues()) {
-        LOG.debug("Expected changeset item {}", changesetItemId);
-        final ODataResponse res = chgres.next();
-        if (res.getStatusCode() >= 400) {
-          throw new IllegalStateException("Transaction failed: " + res.getStatusMessage());
-        }
-
-        final EntityInvocationHandler handler = items.get(changesetItemId);
-
-        if (handler != null) {
-          if (res instanceof ODataEntityCreateResponse && res.getStatusCode() == 201) {
-            handler.setEntity(((ODataEntityCreateResponse) res).getBody());
-            LOG.debug("Upgrade created object '{}'", handler);
-          } else if (res instanceof ODataEntityUpdateResponse && res.getStatusCode() == 200) {
-            handler.setEntity(((ODataEntityUpdateResponse) res).getBody());
-            LOG.debug("Upgrade updated object '{}'", handler);
-          }
-        }
-      }
-    }
-
-    factory.getContext().detachAll();
-  }
-
-  private AttachedEntityStatus batch(
-          final EntityInvocationHandler handler,
-          final CommonODataEntity entity,
-          final ODataChangeset changeset) {
-
-    switch (factory.getContext().entityContext().getStatus(handler)) {
-      case NEW:
-        batchCreate(handler, entity, changeset);
-        return AttachedEntityStatus.NEW;
-
-      case CHANGED:
-        batchUpdate(handler, entity, changeset);
-        return AttachedEntityStatus.CHANGED;
-
-      case DELETED:
-        batchDelete(handler, entity, changeset);
-        return AttachedEntityStatus.DELETED;
-
-      default:
-        if (handler.isChanged()) {
-          batchUpdate(handler, entity, changeset);
-        }
-        return AttachedEntityStatus.CHANGED;
-    }
-  }
-
-  private void batchCreate(
-          final EntityInvocationHandler handler,
-          final CommonODataEntity entity,
-          final ODataChangeset changeset) {
-
-    LOG.debug("Create '{}'", handler);
-
-    changeset.addRequest(factory.getClient().getCUDRequestFactory().
-            getEntityCreateRequest(handler.getEntitySetURI(), entity));
-  }
-
-  private void batchUpdateMediaEntity(
-          final EntityInvocationHandler handler,
-          final URI uri,
-          final InputStream input,
-          final ODataChangeset changeset) {
-
-    LOG.debug("Update media entity '{}'", uri);
-
-    final ODataMediaEntityUpdateRequest<?> req =
-            factory.getClient().getCUDRequestFactory().getMediaEntityUpdateRequest(uri, input);
-
-    req.setContentType(StringUtils.isBlank(handler.getEntity().getMediaContentType())
-            ? ODataMediaFormat.WILDCARD.toString()
-            : ODataMediaFormat.fromFormat(handler.getEntity().getMediaContentType()).toString());
-
-    if (StringUtils.isNotBlank(handler.getETag())) {
-      req.setIfMatch(handler.getETag());
-    }
-
-    changeset.addRequest(req);
-  }
-
-  private void batchUpdateMediaResource(
-          final EntityInvocationHandler handler,
-          final URI uri,
-          final InputStream input,
-          final ODataChangeset changeset) {
-
-    LOG.debug("Update media entity '{}'", uri);
-
-    final ODataStreamUpdateRequest req = factory.getClient().getCUDRequestFactory().getStreamUpdateRequest(uri, input);
-
-    if (StringUtils.isNotBlank(handler.getETag())) {
-      req.setIfMatch(handler.getETag());
-    }
-
-    changeset.addRequest(req);
-  }
-
-  private void batchUpdate(
-          final EntityInvocationHandler handler,
-          final CommonODataEntity changes,
-          final ODataChangeset changeset) {
-
-    LOG.debug("Update '{}'", handler.getEntityURI());
-
-    final ODataEntityUpdateRequest<CommonODataEntity> req =
-            factory.getClient().getServiceVersion().compareTo(ODataServiceVersion.V30) <= 0
-            ? ((org.apache.olingo.client.api.v3.EdmEnabledODataClient) factory.getClient()).getCUDRequestFactory().
-            getEntityUpdateRequest(handler.getEntityURI(),
-                    org.apache.olingo.client.api.communication.request.cud.v3.UpdateType.PATCH, changes)
-            : ((org.apache.olingo.client.api.v4.EdmEnabledODataClient) factory.getClient()).getCUDRequestFactory().
-            getEntityUpdateRequest(handler.getEntityURI(),
-                    org.apache.olingo.client.api.communication.request.cud.v4.UpdateType.PATCH, changes);
-
-    req.setPrefer(new ODataPreferences(factory.getClient().getServiceVersion()).returnContent());
-
-    if (StringUtils.isNotBlank(handler.getETag())) {
-      req.setIfMatch(handler.getETag());
-    }
-
-    changeset.addRequest(req);
-  }
-
-  private void batchUpdate(
-          final EntityInvocationHandler handler,
-          final URI uri,
-          final CommonODataEntity changes,
-          final ODataChangeset changeset) {
-
-    LOG.debug("Update '{}'", uri);
-
-    final ODataEntityUpdateRequest<CommonODataEntity> req =
-            factory.getClient().getServiceVersion().compareTo(ODataServiceVersion.V30) <= 0
-            ? ((org.apache.olingo.client.api.v3.EdmEnabledODataClient) factory.getClient()).getCUDRequestFactory().
-            getEntityUpdateRequest(uri,
-                    org.apache.olingo.client.api.communication.request.cud.v3.UpdateType.PATCH, changes)
-            : ((org.apache.olingo.client.api.v4.EdmEnabledODataClient) factory.getClient()).getCUDRequestFactory().
-            getEntityUpdateRequest(uri,
-                    org.apache.olingo.client.api.communication.request.cud.v4.UpdateType.PATCH, changes);
-
-    req.setPrefer(new ODataPreferences(factory.getClient().getServiceVersion()).returnContent());
-
-    if (StringUtils.isNotBlank(handler.getETag())) {
-      req.setIfMatch(handler.getETag());
-    }
-
-    changeset.addRequest(req);
-  }
-
-  private void batchDelete(
-          final EntityInvocationHandler handler,
-          final CommonODataEntity entity,
-          final ODataChangeset changeset) {
-
-    final URI deleteURI = handler.getEntityURI() == null ? entity.getEditLink() : handler.getEntityURI();
-    LOG.debug("Delete '{}'", deleteURI);
-
-    final ODataDeleteRequest req = factory.getClient().getCUDRequestFactory().getDeleteRequest(deleteURI);
-
-    if (StringUtils.isNotBlank(handler.getETag())) {
-      req.setIfMatch(handler.getETag());
-    }
-
-    changeset.addRequest(req);
-  }
-
-  private int processEntityContext(
-          final EntityInvocationHandler handler,
-          int pos,
-          final TransactionItems items,
-          final List<EntityLinkDesc> delayedUpdates,
-          final ODataChangeset changeset) {
-
-    LOG.debug("Process '{}'", handler);
-
-    items.put(handler, null);
-
-    final CommonODataEntity entity = handler.getEntity();
-    entity.getNavigationLinks().clear();
-
-    final AttachedEntityStatus currentStatus = factory.getContext().entityContext().getStatus(handler);
-
-    if (AttachedEntityStatus.DELETED != currentStatus) {
-      entity.getProperties().clear();
-      CoreUtils.addProperties(factory.getClient(), handler.getPropertyChanges(), entity);
-
-      if (entity instanceof ODataEntity) {
-        ((ODataEntity) entity).getAnnotations().clear();
-        CoreUtils.addAnnotations(factory.getClient(), handler.getAnnotations(), (ODataEntity) entity);
-
-        for (Map.Entry<String, AnnotatableInvocationHandler> entry : handler.getPropAnnotatableHandlers().entrySet()) {
-          CoreUtils.addAnnotations(factory.getClient(),
-                  entry.getValue().getAnnotations(), ((ODataEntity) entity).getProperty(entry.getKey()));
-        }
-      }
-    }
-
-    for (Map.Entry<NavigationProperty, Object> property : handler.getLinkChanges().entrySet()) {
-      final ODataLinkType type = Collection.class.isAssignableFrom(property.getValue().getClass())
-              ? ODataLinkType.ENTITY_SET_NAVIGATION
-              : ODataLinkType.ENTITY_NAVIGATION;
-
-      final Set<EntityInvocationHandler> toBeLinked = new HashSet<EntityInvocationHandler>();
-      for (Object proxy : type == ODataLinkType.ENTITY_SET_NAVIGATION
-              ? (Collection) property.getValue() : Collections.singleton(property.getValue())) {
-
-        final EntityInvocationHandler target = (EntityInvocationHandler) Proxy.getInvocationHandler(proxy);
-
-        final AttachedEntityStatus status = factory.getContext().entityContext().getStatus(target);
-
-        final URI editLink = target.getEntity().getEditLink();
-
-        if ((status == AttachedEntityStatus.ATTACHED || status == AttachedEntityStatus.LINKED) && !target.isChanged()) {
-          entity.addLink(buildNavigationLink(
-                  property.getKey().name(),
-                  URIUtils.getURI(factory.getClient().getServiceRoot(), editLink.toASCIIString()), type));
-        } else {
-          if (!items.contains(target)) {
-            pos = processEntityContext(target, pos, items, delayedUpdates, changeset);
-            pos++;
-          }
-
-          final Integer targetPos = items.get(target);
-          if (targetPos == null) {
-            // schedule update for the current object
-            LOG.debug("Schedule '{}' from '{}' to '{}'", type.name(), handler, target);
-            toBeLinked.add(target);
-          } else if (status == AttachedEntityStatus.CHANGED) {
-            entity.addLink(buildNavigationLink(
-                    property.getKey().name(),
-                    URIUtils.getURI(factory.getClient().getServiceRoot(), editLink.toASCIIString()), type));
-          } else {
-            // create the link for the current object
-            LOG.debug("'{}' from '{}' to (${}) '{}'", type.name(), handler, targetPos, target);
-
-            entity.addLink(buildNavigationLink(property.getKey().name(), URI.create("$" + targetPos), type));
-          }
-        }
-      }
-
-      if (!toBeLinked.isEmpty()) {
-        delayedUpdates.add(new EntityLinkDesc(property.getKey().name(), handler, toBeLinked, type));
-      }
-    }
-
-    if (entity instanceof ODataEntity) {
-      for (Map.Entry<String, AnnotatableInvocationHandler> entry
-              : handler.getNavPropAnnotatableHandlers().entrySet()) {
-
-        CoreUtils.addAnnotations(factory.getClient(),
-                entry.getValue().getAnnotations(),
-                (org.apache.olingo.commons.api.domain.v4.ODataLink) entity.getNavigationLink(entry.getKey()));
-      }
-    }
-
-    // insert into the batch
-    LOG.debug("{}: Insert '{}' into the batch", pos, handler);
-    final AttachedEntityStatus processedStatus = batch(handler, entity, changeset);
-
-    items.put(handler, pos);
-
-    if (processedStatus != AttachedEntityStatus.DELETED) {
-      int startingPos = pos;
-
-      if (handler.getEntity().isMediaEntity() && handler.isChanged()) {
-        // update media properties
-        if (!handler.getPropertyChanges().isEmpty()) {
-          final URI targetURI = currentStatus == AttachedEntityStatus.NEW
-                  ? URI.create("$" + startingPos)
-                  : URIUtils.getURI(
-                          factory.getClient().getServiceRoot(), handler.getEntity().getEditLink().toASCIIString());
-          batchUpdate(handler, targetURI, entity, changeset);
-          pos++;
-          items.put(handler, pos);
-        }
-
-        // update media content
-        if (handler.getStreamChanges() != null) {
-          final URI targetURI = currentStatus == AttachedEntityStatus.NEW
-                  ? URI.create("$" + startingPos + "/$value")
-                  : URIUtils.getURI(
-                          factory.getClient().getServiceRoot(),
-                          handler.getEntity().getEditLink().toASCIIString() + "/$value");
-
-          batchUpdateMediaEntity(handler, targetURI, handler.getStreamChanges(), changeset);
-
-          // update media info (use null key)
-          pos++;
-          items.put(null, pos);
-        }
-      }
-
-      for (Map.Entry<String, InputStream> streamedChanges : handler.getStreamedPropertyChanges().entrySet()) {
-        final URI targetURI = currentStatus == AttachedEntityStatus.NEW
-                ? URI.create("$" + startingPos) : URIUtils.getURI(
-                        factory.getClient().getServiceRoot(),
-                        CoreUtils.getMediaEditLink(streamedChanges.getKey(), entity).toASCIIString());
-
-        batchUpdateMediaResource(handler, targetURI, streamedChanges.getValue(), changeset);
-
-        // update media info (use null key)
-        pos++;
-        items.put(handler, pos);
-      }
-    }
-
-    return pos;
-  }
-
-  private ODataLink buildNavigationLink(final String name, final URI uri, final ODataLinkType type) {
-    ODataLink result;
-
-    switch (type) {
-      case ENTITY_NAVIGATION:
-        result = factory.getClient().getObjectFactory().newEntityNavigationLink(name, uri);
-        break;
-
-      case ENTITY_SET_NAVIGATION:
-        result = factory.getClient().getObjectFactory().newEntitySetNavigationLink(name, uri);
-        break;
-
-      default:
-        throw new IllegalArgumentException("Invalid link type " + type.name());
-    }
-
-    return result;
-  }
-
-  private void processDelayedUpdates(
-          final List<EntityLinkDesc> delayedUpdates,
-          int pos,
-          final TransactionItems items,
-          final ODataChangeset changeset) {
-
-    for (EntityLinkDesc delayedUpdate : delayedUpdates) {
-      pos++;
-      items.put(delayedUpdate.getSource(), pos);
-
-      final CommonODataEntity changes =
-              factory.getClient().getObjectFactory().newEntity(delayedUpdate.getSource().getEntity().getTypeName());
-
-      AttachedEntityStatus status = factory.getContext().entityContext().getStatus(delayedUpdate.getSource());
-
-      final URI sourceURI;
-      if (status == AttachedEntityStatus.CHANGED) {
-        sourceURI = URIUtils.getURI(
-                factory.getClient().getServiceRoot(),
-                delayedUpdate.getSource().getEntity().getEditLink().toASCIIString());
-      } else {
-        int sourcePos = items.get(delayedUpdate.getSource());
-        sourceURI = URI.create("$" + sourcePos);
-      }
-
-      for (EntityInvocationHandler target : delayedUpdate.getTargets()) {
-        status = factory.getContext().entityContext().getStatus(target);
-
-        final URI targetURI;
-        if (status == AttachedEntityStatus.CHANGED) {
-          targetURI = URIUtils.getURI(
-                  factory.getClient().getServiceRoot(), target.getEntity().getEditLink().toASCIIString());
-        } else {
-          int targetPos = items.get(target);
-          targetURI = URI.create("$" + targetPos);
-        }
-
-        changes.addLink(delayedUpdate.getType() == ODataLinkType.ENTITY_NAVIGATION
-                ? factory.getClient().getObjectFactory().
-                newEntityNavigationLink(delayedUpdate.getSourceName(), targetURI)
-                : factory.getClient().getObjectFactory().
-                newEntitySetNavigationLink(delayedUpdate.getSourceName(), targetURI));
-
-        LOG.debug("'{}' from {} to {}", delayedUpdate.getType().name(), sourceURI, targetURI);
-      }
-
-      batchUpdate(delayedUpdate.getSource(), sourceURI, changes, changeset);
-    }
-  }
-
-  private class TransactionItems {
-
-    private final List<EntityInvocationHandler> keys = new ArrayList<EntityInvocationHandler>();
-
-    private final List<Integer> values = new ArrayList<Integer>();
-
-    public EntityInvocationHandler get(final Integer value) {
-      if (value != null && values.contains(value)) {
-        return keys.get(values.indexOf(value));
-      } else {
-        return null;
-      }
-    }
-
-    public Integer get(final EntityInvocationHandler key) {
-      if (key != null && keys.contains(key)) {
-        return values.get(keys.indexOf(key));
-      } else {
-        return null;
-      }
-    }
-
-    public void remove(final EntityInvocationHandler key) {
-      if (keys.contains(key)) {
-        values.remove(keys.indexOf(key));
-        keys.remove(key);
-      }
-    }
-
-    public void put(final EntityInvocationHandler key, final Integer value) {
-      // replace just in case of null current value; otherwise add the new entry
-      if (key != null && keys.contains(key) && values.get(keys.indexOf(key)) == null) {
-        remove(key);
-      }
-      keys.add(key);
-      values.add(value);
-    }
-
-    public List<Integer> sortedValues() {
-      final List<Integer> sortedValues = new ArrayList<Integer>(values);
-      Collections.<Integer>sort(sortedValues);
-      return sortedValues;
-    }
-
-    public boolean contains(final EntityInvocationHandler key) {
-      return keys.contains(key);
-    }
-
-    public int size() {
-      return keys.size();
-    }
-
-    public boolean isEmpty() {
-      return keys.isEmpty();
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/a66bd98b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/TransactionItems.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/TransactionItems.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/TransactionItems.java
new file mode 100644
index 0000000..3e5ab6f
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/TransactionItems.java
@@ -0,0 +1,81 @@
+/*
+ * 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.ext.proxy.commons;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class TransactionItems {
+
+  private final List<EntityInvocationHandler> keys = new ArrayList<EntityInvocationHandler>();
+
+  private final List<Integer> values = new ArrayList<Integer>();
+
+  public EntityInvocationHandler get(final Integer value) {
+    if (value != null && values.contains(value)) {
+      return keys.get(values.indexOf(value));
+    } else {
+      return null;
+    }
+  }
+
+  public Integer get(final EntityInvocationHandler key) {
+    if (key != null && keys.contains(key)) {
+      return values.get(keys.indexOf(key));
+    } else {
+      return null;
+    }
+  }
+
+  public void remove(final EntityInvocationHandler key) {
+    if (keys.contains(key)) {
+      values.remove(keys.indexOf(key));
+      keys.remove(key);
+    }
+  }
+
+  public void put(final EntityInvocationHandler key, final Integer value) {
+    // replace just in case of null current value; otherwise add the new entry
+    if (key != null && keys.contains(key) && values.get(keys.indexOf(key)) == null) {
+      remove(key);
+    }
+    keys.add(key);
+    values.add(value);
+  }
+
+  public List<Integer> sortedValues() {
+    final List<Integer> sortedValues = new ArrayList<Integer>(values);
+    Collections.<Integer>sort(sortedValues);
+    return sortedValues;
+  }
+
+  public boolean contains(final EntityInvocationHandler key) {
+    return keys.contains(key);
+  }
+
+  public int size() {
+    return keys.size();
+  }
+
+  public boolean isEmpty() {
+    return keys.isEmpty();
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/a66bd98b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/TransactionalPersistenceManagerImpl.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/TransactionalPersistenceManagerImpl.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/TransactionalPersistenceManagerImpl.java
new file mode 100644
index 0000000..73a3f29
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/TransactionalPersistenceManagerImpl.java
@@ -0,0 +1,108 @@
+/*
+ * 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.ext.proxy.commons;
+
+import java.util.Iterator;
+import java.util.Map;
+import org.apache.olingo.client.api.communication.request.ODataBatchableRequest;
+import org.apache.olingo.client.api.communication.request.ODataRequest;
+import org.apache.olingo.client.api.communication.request.ODataStreamedRequest;
+import org.apache.olingo.client.api.communication.request.batch.BatchManager;
+import org.apache.olingo.client.api.communication.request.batch.CommonODataBatchRequest;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchResponseItem;
+import org.apache.olingo.client.api.communication.request.batch.ODataChangeset;
+import org.apache.olingo.client.api.communication.response.ODataBatchResponse;
+import org.apache.olingo.client.api.communication.response.ODataEntityCreateResponse;
+import org.apache.olingo.client.api.communication.response.ODataEntityUpdateResponse;
+import org.apache.olingo.client.api.communication.response.ODataResponse;
+import org.apache.olingo.client.core.communication.request.batch.ODataChangesetResponseItem;
+import org.apache.olingo.ext.proxy.EntityContainerFactory;
+
+/**
+ * {@link org.apache.olingo.ext.proxy.api.PersistenceManager} implementation using OData batch requests to implement
+ * high-level user transactions: all read-write operations will be packed in a batch request to the OData service when
+ * calling <tt>flush()</tt>.
+ */
+public class TransactionalPersistenceManagerImpl extends AbstractPersistenceManager {
+
+  private static final long serialVersionUID = -3320312269235907501L;
+
+  public TransactionalPersistenceManagerImpl(final EntityContainerFactory<?> factory) {
+    super(factory);
+  }
+
+  /**
+   * Transactional changes commit.
+   */
+  @Override
+  protected void doFlush(final PersistenceChanges changes, final TransactionItems items) {
+    final CommonODataBatchRequest request =
+            factory.getClient().getBatchRequestFactory().getBatchRequest(factory.getClient().getServiceRoot());
+    ((ODataRequest) request).setAccept(factory.getClient().getConfiguration().getDefaultBatchAcceptFormat());
+
+    final BatchManager streamManager = (BatchManager) ((ODataStreamedRequest) request).payloadManager();
+
+    final ODataChangeset changeset = streamManager.addChangeset();
+    for (Map.Entry<ODataBatchableRequest, EntityInvocationHandler> entry : changes.getChanges().entrySet()) {
+      changeset.addRequest(entry.getKey());
+    }
+
+    final ODataBatchResponse response = streamManager.getResponse();
+
+    // This should be 202 for service version <= 3.0 and 200 for service version >= 4.0 but it seems that
+    // many service implementations are not fully compliant in this respect.
+    if (response.getStatusCode() != 202 && response.getStatusCode() != 200) {
+      throw new IllegalStateException("Operation failed");
+    }
+
+    if (!items.isEmpty()) {
+      final Iterator<ODataBatchResponseItem> iter = response.getBody();
+      if (!iter.hasNext()) {
+        throw new IllegalStateException("Unexpected operation result");
+      }
+
+      final ODataBatchResponseItem item = iter.next();
+      if (!(item instanceof ODataChangesetResponseItem)) {
+        throw new IllegalStateException("Unexpected batch response item " + item.getClass().getSimpleName());
+      }
+
+      final ODataChangesetResponseItem chgres = (ODataChangesetResponseItem) item;
+
+      for (Integer changesetItemId : items.sortedValues()) {
+        LOG.debug("Expected changeset item {}", changesetItemId);
+        final ODataResponse res = chgres.next();
+        if (res.getStatusCode() >= 400) {
+          throw new IllegalStateException("Transaction failed: " + res.getStatusMessage());
+        }
+
+        final EntityInvocationHandler handler = items.get(changesetItemId);
+
+        if (handler != null) {
+          if (res instanceof ODataEntityCreateResponse && res.getStatusCode() == 201) {
+            handler.setEntity(((ODataEntityCreateResponse) res).getBody());
+            LOG.debug("Upgrade created object '{}'", handler);
+          } else if (res instanceof ODataEntityUpdateResponse && res.getStatusCode() == 200) {
+            handler.setEntity(((ODataEntityUpdateResponse) res).getBody());
+            LOG.debug("Upgrade updated object '{}'", handler);
+          }
+        }
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/a66bd98b/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 e4a2c73..ce4d3ff 100644
--- a/fit/src/main/java/org/apache/olingo/fit/V4Services.java
+++ b/fit/src/main/java/org/apache/olingo/fit/V4Services.java
@@ -374,7 +374,7 @@ public class V4Services extends AbstractServices {
     return StringUtils.isBlank(filter) && StringUtils.isBlank(search)
             ? NumberUtils.isNumber(type)
             ? super.getEntityInternal(
-            uriInfo.getRequestUri().toASCIIString(), accept, "People", type, format, null, null, true)
+                    uriInfo.getRequestUri().toASCIIString(), accept, "People", type, format, null, null, true)
             : super.getEntitySet(accept, "People", type)
             : super.getEntitySet(uriInfo, accept, "People", top, skip, format, count, filter, orderby, skiptoken);
   }
@@ -390,6 +390,43 @@ public class V4Services extends AbstractServices {
             uriInfo.getRequestUri().toASCIIString(), accept, "Boss", StringUtils.EMPTY, format, null, null, false);
   }
 
+  @DELETE
+  @Path("/Orders({entityId})/CustomerForOrder")
+  public Response getCustomerForOrder(
+          @Context UriInfo uriInfo,
+          @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
+          @PathParam("entityId") String entityId,
+          @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) String format) {
+
+    try {
+      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("Orders", entityId, Accept.ATOM);
+
+      final InputStream entity = entityInfo.getValue();
+
+      ResWrap<AtomEntityImpl> container = atomDeserializer.read(entity, AtomEntityImpl.class);
+      if (container.getContextURL() == null) {
+        container = new ResWrap<AtomEntityImpl>(URI.create(Constants.get(version, ConstantKey.ODATA_METADATA_PREFIX)
+                + "Orders" + Constants.get(version, ConstantKey.ODATA_METADATA_ENTITY_SUFFIX)),
+                container.getMetadataETag(), container.getPayload());
+      }
+      final Entity entry = container.getPayload();
+      
+      entry.getNavigationLink("CustomerForOrder");
+    } catch (Exception e) {
+
+    }
+
+    return getEntityInternal(
+            uriInfo.getRequestUri().toASCIIString(), accept, "Boss", StringUtils.EMPTY, format, null, null, false);
+  }
+
   @GET
   @Path("/Company")
   public Response getSingletonCompany(
@@ -748,7 +785,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) {
@@ -770,7 +807,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);
@@ -868,8 +905,8 @@ public class V4Services extends AbstractServices {
       } else {
         final ResWrap<JSONEntityImpl> jcontainer =
                 mapper.readValue(IOUtils.toInputStream(entity, Constants.ENCODING),
-                new TypeReference<JSONEntityImpl>() {
-        });
+                        new TypeReference<JSONEntityImpl>() {
+                        });
 
         entry = dataBinder.toAtomEntity(jcontainer.getPayload());
 
@@ -967,7 +1004,7 @@ public class V4Services extends AbstractServices {
 
         final ResWrap<JSONEntityImpl> jsonContainer = mapper.readValue(
                 IOUtils.toInputStream(changes, Constants.ENCODING), new TypeReference<JSONEntityImpl>() {
-        });
+                });
         jsonContainer.getPayload().setType(typeInfo.getFullQualifiedName().toString());
         entryChanges = dataBinder.toAtomEntity(jsonContainer.getPayload());
       }
@@ -1001,7 +1038,7 @@ public class V4Services extends AbstractServices {
       // 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);
+                      append('(').append(containedEntityId).append(')').toString(), Accept.ATOM);
       final ResWrap<AtomEntityImpl> container = atomDeserializer.read(entry, AtomEntityImpl.class);
 
       // 2. Remove the contained entity
@@ -1247,8 +1284,8 @@ public class V4Services extends AbstractServices {
       } else {
         final ResWrap<JSONPropertyImpl> paramContainer =
                 mapper.readValue(IOUtils.toInputStream(param, Constants.ENCODING),
-                new TypeReference<JSONPropertyImpl>() {
-        });
+                        new TypeReference<JSONPropertyImpl>() {
+                        });
         property = paramContainer.getPayload();
       }
 
@@ -1288,7 +1325,7 @@ public class V4Services extends AbstractServices {
 
       final ResWrap<AtomPropertyImpl> result = new ResWrap<AtomPropertyImpl>(
               URI.create(Constants.get(version, ConstantKey.ODATA_METADATA_PREFIX)
-              + "Microsoft.Test.OData.Services.ODataWCFService.Address"),
+                      + "Microsoft.Test.OData.Services.ODataWCFService.Address"),
               null,
               (AtomPropertyImpl) entity.getProperty("address"));
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/a66bd98b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/AbstractTestITCase.java
----------------------------------------------------------------------
diff --git a/fit/src/test/java/org/apache/olingo/fit/proxy/v4/AbstractTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/AbstractTestITCase.java
index 931fcf4..2bfcdf5 100644
--- a/fit/src/test/java/org/apache/olingo/fit/proxy/v4/AbstractTestITCase.java
+++ b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/AbstractTestITCase.java
@@ -91,6 +91,7 @@ public abstract class AbstractTestITCase {
 
   protected void createAndDeleteOrder(
           final InMemoryEntities container, final EntityContainerFactory<EdmEnabledODataClient> containerFactory) {
+    
     final Order order = container.getOrders().newOrder();
     order.setOrderID(105);
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/a66bd98b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/EntityCreateTestITCase.java
----------------------------------------------------------------------
diff --git a/fit/src/test/java/org/apache/olingo/fit/proxy/v4/EntityCreateTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/EntityCreateTestITCase.java
index 3dadd6a..959e62b 100644
--- a/fit/src/test/java/org/apache/olingo/fit/proxy/v4/EntityCreateTestITCase.java
+++ b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/EntityCreateTestITCase.java
@@ -178,7 +178,7 @@ public class EntityCreateTestITCase extends AbstractTestITCase {
     customer.setLastName("Martelli");
     customer.setCity("Pescara");
     customer.setEmails(Collections.<String>singleton("fabio.martelli@tirasa.net"));
-    Address homeAddress = customer.factory().newHomeAddress();
+    final Address homeAddress = customer.factory().newHomeAddress();
     homeAddress.setCity("Pescara");
     homeAddress.setPostalCode("65100");
     homeAddress.setStreet("viale Gabriele D'Annunzio 256");

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/a66bd98b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/EntityUpdateTestITCase.java
----------------------------------------------------------------------
diff --git a/fit/src/test/java/org/apache/olingo/fit/proxy/v4/EntityUpdateTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/EntityUpdateTestITCase.java
index 943eda6..10d9a03 100644
--- a/fit/src/test/java/org/apache/olingo/fit/proxy/v4/EntityUpdateTestITCase.java
+++ b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/EntityUpdateTestITCase.java
@@ -18,15 +18,22 @@
  */
 package org.apache.olingo.fit.proxy.v4;
 
+import static org.apache.olingo.fit.proxy.v4.AbstractTestITCase.container;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import java.lang.reflect.Proxy;
 import java.math.BigDecimal;
+import java.util.Calendar;
+import java.util.Collections;
 import java.util.UUID;
+import org.apache.commons.lang3.RandomUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.olingo.client.api.v4.EdmEnabledODataClient;
+import org.apache.olingo.ext.proxy.EntityContainerFactory;
 import org.apache.olingo.ext.proxy.commons.EntityInvocationHandler;
+import org.apache.olingo.fit.proxy.v4.staticservice.microsoft.test.odata.services.odatawcfservice.InMemoryEntities;
 import org.apache.olingo.fit.proxy.v4.staticservice.microsoft.test.odata.services.odatawcfservice.types.Address;
 import org.apache.olingo.fit.proxy.v4.staticservice.microsoft.test.odata.services.odatawcfservice.types.Order;
 import org.apache.olingo.fit.proxy.v4.staticservice.microsoft.test.odata.services.odatawcfservice.types.OrderCollection;
@@ -43,16 +50,24 @@ import org.junit.Test;
  */
 public class EntityUpdateTestITCase extends AbstractTestITCase {
 
+  protected EntityContainerFactory<EdmEnabledODataClient> getContainerFactory() {
+    return containerFactory;
+  }
+
+  protected InMemoryEntities getContainer() {
+    return container;
+  }
+
   @Test
   public void update() {
-    Person person = container.getPeople().get(1);
+    Person person = getContainer().getPeople().get(1);
 
     final Address address = person.getHomeAddress();
     address.setCity("XXX");
 
-    container.flush();
+    getContainer().flush();
 
-    person = container.getPeople().get(1);
+    person = getContainer().getPeople().get(1);
     assertEquals("XXX", person.getHomeAddress().getCity());
   }
 
@@ -62,74 +77,106 @@ public class EntityUpdateTestITCase extends AbstractTestITCase {
     orderDetailKey.setOrderID(7);
     orderDetailKey.setProductID(5);
 
-    OrderDetail orderDetail = container.getOrderDetails().get(orderDetailKey);
+    OrderDetail orderDetail = getContainer().getOrderDetails().get(orderDetailKey);
     assertNotNull(orderDetail);
     assertEquals(7, orderDetail.getOrderID(), 0);
     assertEquals(5, orderDetail.getProductID(), 0);
 
     orderDetail.setQuantity(5);
 
-    container.flush();
+    getContainer().flush();
 
-    orderDetail = container.getOrderDetails().get(orderDetailKey);
+    orderDetail = getContainer().getOrderDetails().get(orderDetailKey);
     orderDetail.setQuantity(5);
   }
 
   @Test
   public void patchLink() {
-    Order order = container.getOrders().newOrder();
-    order.setOrderID(400);
-
-    OrderCollection orders = container.getOrders().newOrderCollection();
+    // 1. create customer
+    Customer customer = getContainer().getCustomers().newCustomer();
+    customer.setPersonID(977);
+    customer.setFirstName("Test");
+    customer.setLastName("Test");
+
+    final Address homeAddress = getContainer().complexFactory().newCompanyAddress();
+    homeAddress.setStreet("V.le Gabriele D'Annunzio");
+    homeAddress.setCity("Pescara");
+    homeAddress.setPostalCode("65127");
+    customer.setHomeAddress(homeAddress);
+
+    customer.setNumbers(Collections.<String>emptyList());
+    customer.setEmails(Collections.<String>emptyList());
+    customer.setCity("Pescara");
+
+    final Calendar birthday = Calendar.getInstance();
+    birthday.clear();
+    birthday.set(1977, 8, 8);
+    customer.setBirthday(birthday);
+
+    customer.setTimeBetweenLastTwoOrders(BigDecimal.valueOf(0.0000002));    
+    
+    // 2. create order and set it to customer
+    final int orderId = RandomUtils.nextInt(400, 410);
+    
+    Order order = getContainer().getOrders().newOrder();
+    order.setOrderID(orderId);
+
+    final OrderCollection orders = getContainer().getOrders().newOrderCollection();
     orders.add(order);
 
-    Customer customer = container.getCustomers().get(1);
     customer.setOrders(orders);
     order.setCustomerForOrder(customer);
 
-    container.flush();
+    getContainer().flush();
 
-    order = container.getOrders().get(400);
-    assertEquals(400, order.getOrderID().intValue());
+    // 3. check everything after flush
+    order = getContainer().getOrders().get(orderId);
+    assertEquals(orderId, order.getOrderID(), 0);
 
-    customer = container.getCustomers().get(1);
+    customer = getContainer().getCustomers().get(977);
 
-    assertEquals(2, customer.getOrders().size());
+    //assertEquals(1, customer.getOrders().size());
 
     int count = 0;
     for (Order inside : customer.getOrders()) {
-      if (inside.getOrderID() == 400) {
+      if (inside.getOrderID() == orderId) {
         count++;
       }
     }
     assertEquals(1, count);
-    assertEquals(1, order.getCustomerForOrder().getPersonID(), 0);
+    assertEquals(977, order.getCustomerForOrder().getPersonID(), 0);
+    
+    // 4. delete customer and order
+    getContainer().getCustomers().delete(977);
+    getContainer().getOrders().delete(orderId);
+    
+    getContainer().flush();
   }
 
   @Test
   public void concurrentModification() {
-    Order order = container.getOrders().get(8);
+    Order order = getContainer().getOrders().get(8);
     final String etag = ((EntityInvocationHandler) Proxy.getInvocationHandler(order)).getETag();
     assertTrue(StringUtils.isNotBlank(etag));
 
     order.setShelfLife(BigDecimal.TEN);
 
-    container.flush();
+    getContainer().flush();
 
-    order = container.getOrders().get(8);
+    order = getContainer().getOrders().get(8);
     assertEquals(BigDecimal.TEN, order.getShelfLife());
   }
 
   @Test
   public void contained() {
-    PaymentInstrument instrument = container.getAccounts().get(101).getMyPaymentInstruments().get(101901);
+    PaymentInstrument instrument = getContainer().getAccounts().get(101).getMyPaymentInstruments().get(101901);
 
     final String newName = UUID.randomUUID().toString();
     instrument.setFriendlyName(newName);
 
-    container.flush();
+    getContainer().flush();
 
-    instrument = container.getAccounts().get(101).getMyPaymentInstruments().get(101901);
+    instrument = getContainer().getAccounts().get(101).getMyPaymentInstruments().get(101901);
     assertEquals(newName, instrument.getFriendlyName());
   }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/a66bd98b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/MediaEntityTestITCase.java
----------------------------------------------------------------------
diff --git a/fit/src/test/java/org/apache/olingo/fit/proxy/v4/MediaEntityTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/MediaEntityTestITCase.java
index 60074b0..0ffe48a 100644
--- a/fit/src/test/java/org/apache/olingo/fit/proxy/v4/MediaEntityTestITCase.java
+++ b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/MediaEntityTestITCase.java
@@ -34,7 +34,6 @@ import org.apache.olingo.commons.api.format.ContentType;
 import org.apache.olingo.ext.proxy.EntityContainerFactory;
 import org.apache.olingo.fit.proxy.v4.demo.odatademo.DemoService;
 import org.apache.olingo.fit.proxy.v4.demo.odatademo.types.Advertisement;
-import org.junit.BeforeClass;
 import org.junit.Test;
 
 /**
@@ -42,22 +41,30 @@ import org.junit.Test;
  */
 public class MediaEntityTestITCase extends AbstractTestITCase {
 
-  private static DemoService dcontainer;
+  private EntityContainerFactory<EdmEnabledODataClient> ecf;
 
-  @BeforeClass
-  public static void initContainer() {
-    final EntityContainerFactory<EdmEnabledODataClient> otcontainerFactory =
-            EntityContainerFactory.getV4(testDemoServiceRootURL);
-    otcontainerFactory.getClient().getConfiguration().setDefaultBatchAcceptFormat(ContentType.APPLICATION_OCTET_STREAM);
-    dcontainer = otcontainerFactory.getEntityContainer(DemoService.class);
-    assertNotNull(dcontainer);
+  private DemoService ime;
+
+  protected EntityContainerFactory<EdmEnabledODataClient> getContainerFactory() {
+    if (ecf == null) {
+      ecf = EntityContainerFactory.getV4(testDemoServiceRootURL);
+      ecf.getClient().getConfiguration().setDefaultBatchAcceptFormat(ContentType.APPLICATION_OCTET_STREAM);
+    }
+    return ecf;
+  }
+
+  protected DemoService getContainer() {
+    if (ime == null) {
+      ime = getContainerFactory().getEntityContainer(DemoService.class);
+    }
+    return ime;
   }
 
   @Test
   public void read() throws IOException {
     final UUID uuid = UUID.fromString("f89dee73-af9f-4cd4-b330-db93c25ff3c7");
 
-    final Advertisement adv = dcontainer.getAdvertisements().get(uuid);
+    final Advertisement adv = getContainer().getAdvertisements().get(uuid);
     assertTrue(adv.getAirDate() instanceof Calendar);
 
     final InputStream is = adv.getStream();
@@ -69,38 +76,38 @@ public class MediaEntityTestITCase extends AbstractTestITCase {
   public void update() throws IOException {
     final UUID uuid = UUID.fromString("f89dee73-af9f-4cd4-b330-db93c25ff3c7");
 
-    final Advertisement adv = dcontainer.getAdvertisements().get(uuid);
+    final Advertisement adv = getContainer().getAdvertisements().get(uuid);
     assertNotNull(adv);
 
     final String random = RandomStringUtils.random(124, "abcdefghijklmnopqrstuvwxyz");
 
     adv.setStream(IOUtils.toInputStream(random));
 
-    dcontainer.flush();
+    getContainer().flush();
 
-    assertEquals(random, IOUtils.toString(dcontainer.getAdvertisements().get(uuid).getStream()));
+    assertEquals(random, IOUtils.toString(getContainer().getAdvertisements().get(uuid).getStream()));
   }
 
   @Test
   public void create() throws IOException {
     final String random = RandomStringUtils.random(124, "abcdefghijklmnopqrstuvwxyz");
 
-    final Advertisement adv = dcontainer.getAdvertisements().newAdvertisement();
+    final Advertisement adv = getContainer().getAdvertisements().newAdvertisement();
     adv.setStream(IOUtils.toInputStream(random));
     adv.setAirDate(Calendar.getInstance());
 
-    dcontainer.flush();
+    getContainer().flush();
 
     final UUID uuid = adv.getID();
-    containerFactory.getContext().detachAll();
-    
-    assertEquals(random, IOUtils.toString(dcontainer.getAdvertisements().get(uuid).getStream()));
+    getContainerFactory().getContext().detachAll();
+
+    assertEquals(random, IOUtils.toString(getContainer().getAdvertisements().get(uuid).getStream()));
 
-    containerFactory.getContext().detachAll();
+    getContainerFactory().getContext().detachAll();
 
-    dcontainer.getAdvertisements().delete(uuid);
-    dcontainer.flush();
+    getContainer().getAdvertisements().delete(uuid);
+    getContainer().flush();
 
-    assertNull(dcontainer.getAdvertisements().get(uuid));
+    assertNull(getContainer().getAdvertisements().get(uuid));
   }
 }