You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by fm...@apache.org on 2014/08/09 08:46:09 UTC

git commit: [OLINGO-395] fixed

Repository: olingo-odata4
Updated Branches:
  refs/heads/master 5cdf64db2 -> e3d3bde6e


[OLINGO-395] fixed


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

Branch: refs/heads/master
Commit: e3d3bde6e297a7074951c901e7e33290088b5a82
Parents: 5cdf64d
Author: fmartelli <fa...@gmail.com>
Authored: Sat Aug 9 08:45:52 2014 +0200
Committer: fmartelli <fa...@gmail.com>
Committed: Sat Aug 9 08:45:52 2014 +0200

----------------------------------------------------------------------
 .../olingo/ext/proxy/api/AbstractEntitySet.java |  4 +-
 .../commons/AbstractInvocationHandler.java      | 51 +++++++++++++-
 .../commons/AbstractPersistenceManager.java     |  9 +--
 .../AbstractStructuredInvocationHandler.java    | 16 ++---
 .../commons/CompoundKeyElementWrapper.java      | 57 ---------------
 .../proxy/commons/EntityInvocationHandler.java  | 74 ++++++++------------
 .../commons/EntitySetInvocationHandler.java     | 25 +++----
 .../olingo/ext/proxy/context/EntityContext.java | 15 +++-
 .../proxy/utils/CompoundKeyElementWrapper.java  | 51 ++++++++++++++
 .../olingo/ext/proxy/utils/CoreUtils.java       | 53 ++++++++++++++
 .../fit/proxy/v4/APIBasicDesignTestITCase.java  | 58 ++++++++++++++-
 11 files changed, 273 insertions(+), 140 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e3d3bde6/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/AbstractEntitySet.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/AbstractEntitySet.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/AbstractEntitySet.java
index 57ad175..3e98f12 100644
--- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/AbstractEntitySet.java
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/AbstractEntitySet.java
@@ -78,9 +78,9 @@ public interface AbstractEntitySet<
    * Deletes the given entity in a batch.
    *
    * @param <S>
-   * @param entities to be deleted
+   * @param entity to be deleted
    */
-  <S extends T> void delete(S entities);
+  <S extends T> void delete(S entity);
 
   /**
    * Deletes the given entities in a batch.

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e3d3bde6/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 3dcec81..ae30342 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
@@ -32,16 +32,20 @@ import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 import org.apache.olingo.client.api.CommonEdmEnabledODataClient;
 import org.apache.olingo.client.api.uri.CommonURIBuilder;
+import org.apache.olingo.commons.api.domain.CommonODataEntity;
 import org.apache.olingo.commons.api.domain.ODataValue;
 import org.apache.olingo.commons.api.edm.Edm;
 import org.apache.olingo.commons.api.edm.EdmEntityContainer;
 import org.apache.olingo.commons.api.edm.FullQualifiedName;
 import org.apache.olingo.ext.proxy.AbstractService;
 import org.apache.olingo.ext.proxy.api.ComplexType;
+import org.apache.olingo.ext.proxy.api.EntityType;
 import org.apache.olingo.ext.proxy.api.annotations.EntitySet;
+import org.apache.olingo.ext.proxy.api.annotations.Namespace;
 import org.apache.olingo.ext.proxy.api.annotations.Singleton;
 import org.apache.olingo.ext.proxy.context.AttachedEntityStatus;
 import org.apache.olingo.ext.proxy.context.Context;
+import org.apache.olingo.ext.proxy.context.EntityContext;
 import org.apache.olingo.ext.proxy.utils.CoreUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -130,7 +134,52 @@ abstract class AbstractInvocationHandler implements InvocationHandler {
   }
 
   protected boolean isDeleted(final EntityInvocationHandler handler) {
-    return getContext().entityContext().getStatus(handler) == AttachedEntityStatus.DELETED;
+    return (getContext().entityContext().isAttached(handler)
+            && getContext().entityContext().getStatus(handler) == AttachedEntityStatus.DELETED)
+            || getContext().entityContext().getFurtherDeletes().contains(handler.getEntityURI());
+  }
+
+  protected <S extends EntityType<?>> void deleteEntity(final EntityInvocationHandler handler, final URI entitySetURI) {
+    final EntityContext entityContext = getContext().entityContext();
+
+    final URI baseURI = entitySetURI == null ? handler.getEntitySetURI() : entitySetURI;
+
+    if (baseURI == null) {
+      throw new IllegalStateException("Entity base URI not available");
+    }
+
+    final String name = handler.getUUID().getType().
+            getAnnotation(org.apache.olingo.ext.proxy.api.annotations.EntityType.class).name();
+
+    final String namespace = handler.getUUID().getType().getAnnotation(Namespace.class).value();
+
+    final CommonODataEntity template;
+
+    final URI entityURI;
+    if (handler.getEntityURI() == null || handler.getUUID().getKey() == null) {
+      template = service.getClient().getObjectFactory().newEntity(new FullQualifiedName(namespace, name));
+      CoreUtils.addProperties(getClient(), handler.getPropertyChanges(), template);
+      final Object key = CoreUtils.getKey(getClient(), handler, handler.getUUID().getType(), template);
+
+      entityURI = CoreUtils.buildEditLink(getClient(), baseURI.toASCIIString(), template, key).build();
+      template.setEditLink(entityURI);
+    } else {
+      entityURI = handler.getEntityURI();
+      template = handler.getEntity();
+    }
+
+    // https://issues.apache.org/jira/browse/OLINGO-395
+    if (entityContext.isAttached(handler)) {
+      entityContext.addFurtherDeletes(entityURI);
+    } else {
+      if (handler.getUUID().getKey() == null) {
+        // objects created ad-hoc to generate deletion requests
+        handler.updateEntityUUID(baseURI, handler.getUUID().getType(), template);
+      } else {
+        handler.updateUUID(baseURI, handler.getUUID().getType(), handler.getUUID().getKey());
+      }
+      entityContext.attach(handler, AttachedEntityStatus.DELETED, true);
+    }
   }
 
   protected static CommonURIBuilder<?> buildEntitySetURI(

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e3d3bde6/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
index 62b701a..0d5e850 100644
--- 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
@@ -528,20 +528,17 @@ abstract class AbstractPersistenceManager implements PersistenceManager {
           final CommonODataEntity entity,
           final PersistenceChanges changeset) {
     final URI deleteURI = entity.getEditLink() == null ? handler.getEntityURI() : entity.getEditLink();
-    changeset.addChange(buildDeleteRequest(deleteURI, handler.getETag(), changeset), handler);
+    changeset.addChange(buildDeleteRequest(deleteURI, handler.getETag()), handler);
   }
 
   private void queueDelete(
           final URI deleteURI,
           final String etag,
           final PersistenceChanges changeset) {
-    changeset.addChange(buildDeleteRequest(deleteURI, etag, changeset), null);
+    changeset.addChange(buildDeleteRequest(deleteURI, etag), null);
   }
 
-  private ODataDeleteRequest buildDeleteRequest(
-          final URI deleteURI,
-          final String etag,
-          final PersistenceChanges changeset) {
+  private ODataDeleteRequest buildDeleteRequest(final URI deleteURI, final String etag) {
 
     LOG.debug("Delete '{}'", deleteURI);
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e3d3bde6/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 3189842..c6cfe77 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
@@ -251,13 +251,7 @@ public abstract class AbstractStructuredInvocationHandler extends AbstractInvoca
     final EntityContext entityContext = getContext().entityContext();
 
     if (this instanceof EntityInvocationHandler) {
-      final EntityInvocationHandler handler = EntityInvocationHandler.class.cast(this);
-
-      if (entityContext.isAttached(handler)) {
-        entityContext.setStatus(handler, AttachedEntityStatus.DELETED);
-      } else {
-        entityContext.attach(handler, AttachedEntityStatus.DELETED);
-      }
+      deleteEntity(EntityInvocationHandler.class.cast(this), null);
     } else if (baseURI != null) {
       entityContext.addFurtherDeletes(
               getClient().newURIBuilder(baseURI.toASCIIString()).appendValueSegment().build());
@@ -297,10 +291,10 @@ public abstract class AbstractStructuredInvocationHandler extends AbstractInvoca
           res = Proxy.newProxyInstance(
                   Thread.currentThread().getContextClassLoader(),
                   new Class<?>[] {EdmStreamValue.class}, new EdmStreamValueHandler(
-                          baseURI == null
-                          ? null
-                          : getClient().newURIBuilder(baseURI.toASCIIString()).appendPropertySegment(name).build(),
-                          service));
+                  baseURI == null
+                  ? null
+                  : getClient().newURIBuilder(baseURI.toASCIIString()).appendPropertySegment(name).build(),
+                  service));
 
           streamedPropertyCache.put(name, EdmStreamValue.class.cast(res));
         }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e3d3bde6/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/CompoundKeyElementWrapper.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/CompoundKeyElementWrapper.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/CompoundKeyElementWrapper.java
deleted file mode 100644
index 0b78fe1..0000000
--- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/CompoundKeyElementWrapper.java
+++ /dev/null
@@ -1,57 +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.lang.reflect.Method;
-
-class CompoundKeyElementWrapper implements Comparable<CompoundKeyElementWrapper> {
-
-    private final String name;
-
-    private final Method method;
-
-    private final int position;
-
-    protected CompoundKeyElementWrapper(final String name, final Method method, final int position) {
-        this.name = name;
-        this.method = method;
-        this.position = position;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public Method getMethod() {
-        return method;
-    }
-
-    public int getPosition() {
-        return position;
-    }
-
-    @Override
-    public int compareTo(final CompoundKeyElementWrapper other) {
-        if (other == null) {
-            return 1;
-        } else {
-            return getPosition() > other.getPosition() ? 1 : getPosition() == other.getPosition() ? 0 : -1;
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e3d3bde6/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityInvocationHandler.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityInvocationHandler.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityInvocationHandler.java
index e7e11b6..32d3b26 100644
--- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityInvocationHandler.java
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityInvocationHandler.java
@@ -19,7 +19,6 @@
 package org.apache.olingo.ext.proxy.commons;
 
 import java.io.InputStream;
-import java.lang.annotation.Annotation;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
@@ -27,11 +26,8 @@ import java.net.URI;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.olingo.client.api.communication.request.retrieve.ODataEntityRequest;
 import org.apache.olingo.client.api.communication.request.retrieve.ODataMediaRequest;
@@ -48,8 +44,6 @@ import org.apache.olingo.ext.proxy.AbstractService;
 import org.apache.olingo.ext.proxy.api.AbstractTerm;
 import org.apache.olingo.ext.proxy.api.Annotatable;
 import org.apache.olingo.ext.proxy.api.EdmStreamValue;
-import org.apache.olingo.ext.proxy.api.annotations.CompoundKey;
-import org.apache.olingo.ext.proxy.api.annotations.CompoundKeyElement;
 import org.apache.olingo.ext.proxy.api.annotations.EntityType;
 import org.apache.olingo.ext.proxy.api.annotations.Namespace;
 import org.apache.olingo.ext.proxy.api.annotations.NavigationProperty;
@@ -197,15 +191,8 @@ public class EntityInvocationHandler extends AbstractStructuredInvocationHandler
       this.baseURI = entity.getEditLink();
       this.uri = getClient().newURIBuilder(baseURI.toASCIIString());
     } else if (key != null) {
-      final CommonURIBuilder<?> uriBuilder = getClient().newURIBuilder(entitySetURI.toASCIIString());
-
-      if (key.getClass().getAnnotation(CompoundKey.class) == null) {
-        LOG.debug("Append key segment '{}'", key);
-        uriBuilder.appendKeySegment(key);
-      } else {
-        LOG.debug("Append compound key segment '{}'", key);
-        uriBuilder.appendKeySegment(getCompoundKey(key));
-      }
+      final CommonURIBuilder<?> uriBuilder =
+              CoreUtils.buildEditLink(getClient(), entitySetURI.toASCIIString(), entity, key);
 
       this.uri = uriBuilder;
       this.baseURI = this.uri.build();
@@ -225,15 +212,19 @@ public class EntityInvocationHandler extends AbstractStructuredInvocationHandler
     this.internal = entity;
     getEntity().setMediaEntity(typeRef.getAnnotation(EntityType.class).hasStream());
 
-    this.uuid = new EntityUUID(
-            getUUID().getEntitySetURI(),
-            getUUID().getType(),
-            CoreUtils.getKey(getClient(), this, typeRef, entity));
+    final Object key = CoreUtils.getKey(getClient(), this, typeRef, entity);
+
+    this.uuid = new EntityUUID(getUUID().getEntitySetURI(), getUUID().getType(), key);
 
     // fix for OLINGO-353
     if (this.uri == null) {
-      this.baseURI = entity.getEditLink();
-      this.uri = this.baseURI == null ? null : getClient().newURIBuilder(this.baseURI.toASCIIString());
+      final CommonURIBuilder<?> uriBuilder =
+              entity.getEditLink() == null
+              ? CoreUtils.buildEditLink(getClient(), getUUID().getEntitySetURI().toASCIIString(), entity, key)
+              : getClient().newURIBuilder(entity.getEditLink().toASCIIString());
+
+      this.uri = uriBuilder;
+      this.baseURI = this.uri == null ? null : this.uri.build();
     }
 
     this.streamedPropertyChanges.clear();
@@ -250,8 +241,25 @@ public class EntityInvocationHandler extends AbstractStructuredInvocationHandler
     return uuid;
   }
 
+  public EntityUUID updateEntityUUID(final URI entitySetURI, final Class<?> type, final CommonODataEntity entity) {
+    CoreUtils.addProperties(service.getClient(), this.getPropertyChanges(), entity);
+    final Object key = CoreUtils.getKey(getClient(), this, this.getUUID().getType(), entity);
+    return updateUUID(entitySetURI, type, key);
+  }
+
   public EntityUUID updateUUID(final URI entitySetURI, final Class<?> type, final Object key) {
     this.uuid = new EntityUUID(entitySetURI, type, key);
+
+    if (this.uri == null) {
+      final CommonURIBuilder<?> uriBuilder =
+              getEntity().getEditLink() == null
+              ? CoreUtils.buildEditLink(getClient(), entitySetURI.toASCIIString(), getEntity(), key)
+              : getClient().newURIBuilder(getEntity().getEditLink().toASCIIString());
+
+      this.uri = uriBuilder;
+      this.baseURI = this.uri == null ? null : this.uri.build();
+    }
+
     return this.uuid;
   }
 
@@ -474,30 +482,6 @@ public class EntityInvocationHandler extends AbstractStructuredInvocationHandler
     }
   }
 
-  private Map<String, Object> getCompoundKey(final Object key) {
-    final Set<CompoundKeyElementWrapper> elements = new TreeSet<CompoundKeyElementWrapper>();
-
-    for (Method method : key.getClass().getMethods()) {
-      final Annotation annotation = method.getAnnotation(CompoundKeyElement.class);
-      if (annotation instanceof CompoundKeyElement) {
-        elements.add(new CompoundKeyElementWrapper(
-                ((CompoundKeyElement) annotation).name(), method, ((CompoundKeyElement) annotation).position()));
-      }
-    }
-
-    final LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>();
-
-    for (CompoundKeyElementWrapper element : elements) {
-      try {
-        map.put(element.getName(), element.getMethod().invoke(key));
-      } catch (Exception e) {
-        LOG.warn("Error retrieving compound key element '{}' value", element.getName(), e);
-      }
-    }
-
-    return map;
-  }
-
   @Override
   @SuppressWarnings("unchecked")
   protected <T extends CommonODataProperty> List<T> getInternalProperties() {

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e3d3bde6/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntitySetInvocationHandler.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntitySetInvocationHandler.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntitySetInvocationHandler.java
index 72355c3..bf52a85 100644
--- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntitySetInvocationHandler.java
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntitySetInvocationHandler.java
@@ -135,16 +135,18 @@ public class EntitySetInvocationHandler<
               typeRef.getAnnotation(Namespace.class).value(), ClassUtils.getEntityTypeName(typeRef)));
 
       handler = EntityInvocationHandler.getInstance(key, entity, this.baseURI, typeRef, service);
-    } else if (isDeleted(handler)) {
+    }
+
+    if (isDeleted(handler)) {
       // object deleted
       LOG.debug("Object '{}({})' has been deleted", typeRef.getSimpleName(), uuid);
-      handler = null;
+      return null;
+    } else {
+      return (S) Proxy.newProxyInstance(
+              Thread.currentThread().getContextClassLoader(),
+              new Class<?>[] {typeRef},
+              handler);
     }
-
-    return handler == null ? null : (S) Proxy.newProxyInstance(
-            Thread.currentThread().getContextClassLoader(),
-            new Class<?>[] {typeRef},
-            handler);
   }
 
   @Override
@@ -257,14 +259,7 @@ public class EntitySetInvocationHandler<
 
   @Override
   public <S extends T> void delete(final S entity) {
-    final EntityContext entityContext = getContext().entityContext();
-
-    final EntityInvocationHandler handler = (EntityInvocationHandler) Proxy.getInvocationHandler(entity);
-    if (entityContext.isAttached(handler)) {
-      entityContext.setStatus(handler, AttachedEntityStatus.DELETED);
-    } else {
-      entityContext.attach(handler, AttachedEntityStatus.DELETED);
-    }
+    deleteEntity((EntityInvocationHandler) Proxy.getInvocationHandler(entity), this.baseURI);
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e3d3bde6/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityContext.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityContext.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityContext.java
index 945273e..be63762 100644
--- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityContext.java
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityContext.java
@@ -93,11 +93,24 @@ public class EntityContext implements Iterable<AttachedEntity> {
    * @param status status.
    */
   public void attach(final EntityInvocationHandler entity, final AttachedEntityStatus status) {
+    attach(entity, status, false);
+  }
+
+  /**
+   * Attaches an entity with specified status.
+   * <br/>
+   * Use this method to attach an existing entity.
+   *
+   * @param entity entity to be attached.
+   * @param status status.
+   * @param force force attach.
+   */
+  public void attach(final EntityInvocationHandler entity, final AttachedEntityStatus status, final boolean force) {
     if (isAttached(entity)) {
       throw new IllegalStateException("An entity with the same profile has already been attached");
     }
 
-    if (entity.getUUID().getEntitySetURI() != null) {
+    if (force || entity.getUUID().getEntitySetURI() != null) {
       allAttachedEntities.put(entity, status);
 
       if (entity.getUUID().getKey() != null) {

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e3d3bde6/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/CompoundKeyElementWrapper.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/CompoundKeyElementWrapper.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/CompoundKeyElementWrapper.java
new file mode 100644
index 0000000..7880174
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/CompoundKeyElementWrapper.java
@@ -0,0 +1,51 @@
+/**
+ * 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.utils;
+
+import java.lang.reflect.Method;
+
+public class CompoundKeyElementWrapper implements Comparable<CompoundKeyElementWrapper> {
+
+  private final String name;
+
+  private final Method method;
+
+  private final int position;
+
+  public CompoundKeyElementWrapper(final String name, final Method method, final int position) {
+    this.name = name;
+    this.method = method;
+    this.position = position;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public Method getMethod() {
+    return method;
+  }
+
+  public int getPosition() {
+    return position;
+  }
+
+  @Override
+  public int compareTo(final CompoundKeyElementWrapper other) {
+    if (other == null) {
+      return 1;
+    } else {
+      return getPosition() > other.getPosition() ? 1 : getPosition() == other.getPosition() ? 0 : -1;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e3d3bde6/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/CoreUtils.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/CoreUtils.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/CoreUtils.java
index 1fdea3f..64a77a5 100644
--- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/CoreUtils.java
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/CoreUtils.java
@@ -30,8 +30,11 @@ import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.olingo.client.api.CommonEdmEnabledODataClient;
 import org.apache.olingo.client.api.uri.CommonURIBuilder;
@@ -62,6 +65,7 @@ import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory;
 import org.apache.olingo.ext.proxy.AbstractService;
 import org.apache.olingo.ext.proxy.api.AbstractTerm;
 import org.apache.olingo.ext.proxy.api.annotations.ComplexType;
+import org.apache.olingo.ext.proxy.api.annotations.CompoundKey;
 import org.apache.olingo.ext.proxy.api.annotations.CompoundKeyElement;
 import org.apache.olingo.ext.proxy.api.annotations.EnumType;
 import org.apache.olingo.ext.proxy.api.annotations.Key;
@@ -397,6 +401,55 @@ public final class CoreUtils {
     return propertyClass;
   }
 
+  public static CommonURIBuilder<?> buildEditLink(
+          final CommonEdmEnabledODataClient<?> client,
+          final String entitySetURI,
+          final CommonODataEntity entity,
+          final Object key) {
+
+    if (key == null) {
+      return null;
+    }
+
+    final CommonURIBuilder<?> uriBuilder = StringUtils.isNotBlank(entitySetURI)
+            ? client.newURIBuilder(entitySetURI)
+            : client.newURIBuilder();
+
+    if (key.getClass().getAnnotation(CompoundKey.class) == null) {
+      LOG.debug("Append key segment '{}'", key);
+      uriBuilder.appendKeySegment(key);
+    } else {
+      LOG.debug("Append compound key segment '{}'", key);
+      uriBuilder.appendKeySegment(CoreUtils.getCompoundKey(key));
+    }
+
+    return uriBuilder;
+  }
+
+  public static Map<String, Object> getCompoundKey(final Object key) {
+    final Set<CompoundKeyElementWrapper> elements = new TreeSet<CompoundKeyElementWrapper>();
+
+    for (Method method : key.getClass().getMethods()) {
+      final Annotation annotation = method.getAnnotation(CompoundKeyElement.class);
+      if (annotation instanceof CompoundKeyElement) {
+        elements.add(new CompoundKeyElementWrapper(
+                ((CompoundKeyElement) annotation).name(), method, ((CompoundKeyElement) annotation).position()));
+      }
+    }
+
+    final LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>();
+
+    for (CompoundKeyElementWrapper element : elements) {
+      try {
+        map.put(element.getName(), element.getMethod().invoke(key));
+      } catch (Exception e) {
+        LOG.warn("Error retrieving compound key element '{}' value", element.getName(), e);
+      }
+    }
+
+    return map;
+  }
+
   public static Object getKey(
           final CommonEdmEnabledODataClient<?> client,
           final EntityInvocationHandler typeHandler,

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/e3d3bde6/fit/src/test/java/org/apache/olingo/fit/proxy/v4/APIBasicDesignTestITCase.java
----------------------------------------------------------------------
diff --git a/fit/src/test/java/org/apache/olingo/fit/proxy/v4/APIBasicDesignTestITCase.java b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/APIBasicDesignTestITCase.java
index 7d592c6..4804191 100644
--- a/fit/src/test/java/org/apache/olingo/fit/proxy/v4/APIBasicDesignTestITCase.java
+++ b/fit/src/test/java/org/apache/olingo/fit/proxy/v4/APIBasicDesignTestITCase.java
@@ -29,10 +29,12 @@ import java.io.IOException;
 import java.math.BigDecimal;
 import java.sql.Timestamp;
 import java.util.Calendar;
+import java.util.List;
 import java.util.TimeZone;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.olingo.client.api.v4.EdmEnabledODataClient;
+import org.apache.olingo.commons.api.ODataRuntimeException;
 import org.apache.olingo.commons.api.format.ContentType;
 import org.apache.olingo.ext.proxy.AbstractService;
 import org.apache.olingo.ext.proxy.api.EdmStreamValue;
@@ -278,7 +280,7 @@ public class APIBasicDesignTestITCase extends AbstractTestITCase {
 
     service.getContext().detachAll();
     try {
-      getContainer().getOrders().getByKey(105).load();
+      getContainer().getOrders().getByKey(1105).load();
       fail();
     } catch (IllegalArgumentException e) {
     }
@@ -410,7 +412,7 @@ public class APIBasicDesignTestITCase extends AbstractTestITCase {
     // ---------------------------------------
     org.apache.olingo.fit.proxy.v3.staticservice.Service<org.apache.olingo.client.api.v3.EdmEnabledODataClient> v3serv =
             org.apache.olingo.fit.proxy.v3.staticservice.Service.getV3(
-                    "http://localhost:9080/stub/StaticService/V30/Static.svc");
+            "http://localhost:9080/stub/StaticService/V30/Static.svc");
     v3serv.getClient().getConfiguration().setDefaultBatchAcceptFormat(ContentType.APPLICATION_OCTET_STREAM);
     final DefaultContainer v3cont = v3serv.getEntityContainer(DefaultContainer.class);
     assertNotNull(v3cont);
@@ -572,4 +574,56 @@ public class APIBasicDesignTestITCase extends AbstractTestITCase {
     assertNotNull(parent);
     assertEquals(2, parent.getPersonID(), 0);
   }
+
+  /**
+   * Java client should support the deletion based on locally created entity.
+   *
+   * @see https://issues.apache.org/jira/browse/OLINGO-395
+   */
+  @Test
+  public void issueOLINGO395() {
+    Order order = getContainer().newEntityInstance(Order.class);
+    order.setOrderID(1105);
+
+    final Calendar orderDate = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+    orderDate.clear();
+    orderDate.set(2011, 3, 4, 16, 3, 57);
+    order.setOrderDate(new Timestamp(orderDate.getTimeInMillis()));
+
+    order.setShelfLife(BigDecimal.ZERO);
+
+    final PrimitiveCollection<BigDecimal> osl = getContainer().newPrimitiveCollection(BigDecimal.class);
+    osl.add(BigDecimal.TEN.negate());
+    osl.add(BigDecimal.TEN);
+
+    order.setOrderShelfLifes(osl);
+
+    getContainer().getOrders().add(order);
+    getContainer().getOrders().delete(order);
+
+    List<ODataRuntimeException> res = getContainer().flush();
+    assertTrue(res.isEmpty() || res.iterator().next() == null);
+
+    service.getContext().detachAll();
+    try {
+      getContainer().getOrders().getByKey(1105).load();
+      fail();
+    } catch (IllegalArgumentException e) {
+    }
+    service.getContext().detachAll(); // avoid influences
+
+    order = getContainer().newEntityInstance(Order.class);
+    order.setOrderID(1105);
+
+    getContainer().getOrders().delete(order);
+    getContainer().flush(); // test service doesn't fail for delete requests about unexisting objects
+
+    service.getContext().detachAll();
+    try {
+      getContainer().getOrders().getByKey(1105).load();
+      fail();
+    } catch (IllegalArgumentException e) {
+    }
+    service.getContext().detachAll(); // avoid influences
+  }
 }