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/05/07 12:58:12 UTC

[3/3] git commit: [OLINGO-260] providing proxy merge from ODataJClient. Still missing integration tests

[OLINGO-260] providing proxy merge from ODataJClient. Still missing integration tests


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

Branch: refs/heads/master
Commit: 6b7be9deb6af1064f2244a7099737a06ba3fd1d0
Parents: 8caf3fe
Author: fmartelli <fa...@gmail.com>
Authored: Wed May 7 12:57:57 2014 +0200
Committer: fmartelli <fa...@gmail.com>
Committed: Wed May 7 12:57:57 2014 +0200

----------------------------------------------------------------------
 .../ext/proxy/EntityContainerFactory.java       | 110 ++++
 .../olingo/ext/proxy/api/OperationExecutor.java |  27 +
 .../proxy/api/annotations/EntityContainer.java  |   2 +
 .../ext/proxy/api/annotations/Parameter.java    |   5 -
 .../commons/AbstractInvocationHandler.java      | 243 ++++++++
 .../commons/CompoundKeyElementWrapper.java      |  57 ++
 .../olingo/ext/proxy/commons/Container.java     | 544 ++++++++++++++++++
 .../EntityCollectionInvocationHandler.java      | 140 +++++
 .../EntityContainerInvocationHandler.java       | 109 ++++
 .../commons/EntitySetInvocationHandler.java     | 400 +++++++++++++
 .../ext/proxy/commons/EntitySetIterator.java    |  85 +++
 .../commons/EntityTypeInvocationHandler.java    | 564 +++++++++++++++++++
 .../commons/OperationInvocationHandler.java     | 218 +++++++
 .../olingo/ext/proxy/commons/QueryImpl.java     | 173 ++++++
 .../ext/proxy/context/AttachedEntity.java       |  41 ++
 .../ext/proxy/context/AttachedEntityStatus.java |  44 ++
 .../olingo/ext/proxy/context/Context.java       |  37 ++
 .../olingo/ext/proxy/context/EntityContext.java | 199 +++++++
 .../ext/proxy/context/EntityLinkDesc.java       | 104 ++++
 .../olingo/ext/proxy/context/EntityUUID.java    | 102 ++++
 .../olingo/ext/proxy/utils/ClassUtils.java      | 142 +++++
 .../olingo/ext/proxy/utils/EngineUtils.java     | 423 ++++++++++++++
 .../olingo/ext/pojogen/AbstractUtility.java     |   4 +
 .../olingo/ext/pojogen/V3MetadataMojo.java      |   1 +
 .../olingo/ext/pojogen/V4MetadataMojo.java      | 260 ++++-----
 .../src/main/resources/container.vm             |   1 +
 .../client/api/CommonEdmEnabledODataClient.java |   7 +
 .../batch/CommonBatchRequestFactory.java        |   8 +
 .../request/batch/v3/BatchRequestFactory.java   |   8 +-
 .../request/batch/v4/BatchRequestFactory.java   |   8 +-
 .../core/v3/EdmEnabledODataClientImpl.java      |   4 +
 .../core/v4/EdmEnabledODataClientImpl.java      |   7 +-
 32 files changed, 3932 insertions(+), 145 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/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
new file mode 100644
index 0000000..342a30b
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/EntityContainerFactory.java
@@ -0,0 +1,110 @@
+/*
+ * 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;
+
+import java.lang.reflect.Proxy;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.olingo.client.api.CommonEdmEnabledODataClient;
+import org.apache.olingo.client.core.ODataClientFactory;
+import org.apache.olingo.ext.proxy.commons.EntityContainerInvocationHandler;
+import org.apache.olingo.ext.proxy.context.Context;
+
+/**
+ * Entry point for ODataJClient proxy mode, gives access to entity container instances.
+ */
+public class EntityContainerFactory {
+
+  private static final Object MONITOR = new Object();
+
+  private static Context context = null;
+
+  private static final Map<String, EntityContainerFactory> FACTORY_PER_SERVICEROOT =
+          new ConcurrentHashMap<String, EntityContainerFactory>();
+
+  private static final Map<Class<?>, Object> ENTITY_CONTAINERS =
+          new ConcurrentHashMap<Class<?>, Object>();
+
+  private final CommonEdmEnabledODataClient<?> client;
+
+  private final String serviceRoot;
+
+  public static Context getContext() {
+    synchronized (MONITOR) {
+      if (context == null) {
+        context = new Context();
+      }
+    }
+
+    return context;
+  }
+
+  private static <C extends CommonEdmEnabledODataClient<?>> EntityContainerFactory getInstance(
+          final C client, final String serviceRoot) {
+    if (!FACTORY_PER_SERVICEROOT.containsKey(serviceRoot)) {
+      final EntityContainerFactory instance = new EntityContainerFactory(client, serviceRoot);
+      FACTORY_PER_SERVICEROOT.put(serviceRoot, instance);
+    }
+    return FACTORY_PER_SERVICEROOT.get(serviceRoot);
+  }
+
+  public static EntityContainerFactory getV3Instance(final String serviceRoot) {
+    return getInstance(ODataClientFactory.getEdmEnabledV3(serviceRoot), serviceRoot);
+  }
+
+  public static EntityContainerFactory getV4Instance(final String serviceRoot) {
+    return getInstance(ODataClientFactory.getEdmEnabledV4(serviceRoot), serviceRoot);
+  }
+
+  private EntityContainerFactory(final CommonEdmEnabledODataClient<?> client, final String serviceRoot) {
+    this.client = client;
+    this.serviceRoot = serviceRoot;
+  }
+
+  public String getServiceRoot() {
+    return serviceRoot;
+  }
+
+  /**
+   * Return an initialized concrete implementation of the passed EntityContainer interface.
+   *
+   * @param <T> interface annotated as EntityContainer
+   * @param reference class object of the EntityContainer annotated interface
+   * @return an initialized concrete implementation of the passed reference
+   * @throws IllegalStateException if <tt>serviceRoot</tt> was not set
+   * @throws IllegalArgumentException if the passed reference is not an interface annotated as EntityContainer
+   * @see com.msopentech.odatajclient.proxy.api.annotations.EntityContainer
+   */
+  @SuppressWarnings("unchecked")
+  public <T> T getEntityContainer(final Class<T> reference) throws IllegalStateException, IllegalArgumentException {
+    if (StringUtils.isBlank(serviceRoot)) {
+      throw new IllegalStateException("serviceRoot was not set");
+    }
+
+    if (!ENTITY_CONTAINERS.containsKey(reference)) {
+      final Object entityContainer = Proxy.newProxyInstance(
+              Thread.currentThread().getContextClassLoader(),
+              new Class<?>[] {reference},
+              EntityContainerInvocationHandler.getInstance(client, reference, this));
+      ENTITY_CONTAINERS.put(reference, entityContainer);
+    }
+    return (T) ENTITY_CONTAINERS.get(reference);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/OperationExecutor.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/OperationExecutor.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/OperationExecutor.java
new file mode 100644
index 0000000..6914e7a
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/OperationExecutor.java
@@ -0,0 +1,27 @@
+/*
+ * 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.api;
+
+import java.io.Serializable;
+
+/**
+ * Interface for synchronous CRUD operations on an EntitySet.
+ */
+public interface OperationExecutor extends Serializable {
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/annotations/EntityContainer.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/annotations/EntityContainer.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/annotations/EntityContainer.java
index e76cfc8..bf89784 100644
--- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/annotations/EntityContainer.java
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/annotations/EntityContainer.java
@@ -32,5 +32,7 @@ public @interface EntityContainer {
 
   String name();
 
+  String namespace();
+
   boolean isDefaultEntityContainer() default false;
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/annotations/Parameter.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/annotations/Parameter.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/annotations/Parameter.java
index 2ecd79b..ca110b8 100644
--- a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/annotations/Parameter.java
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/api/annotations/Parameter.java
@@ -22,13 +22,10 @@ import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
-import org.apache.olingo.client.api.edm.xml.v3.FunctionImport;
-import org.apache.olingo.client.api.edm.xml.v3.ParameterMode;
 
 /**
  * Function import parameter information.
  *
- * @see FunctionImport
  */
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.PARAMETER)
@@ -40,8 +37,6 @@ public @interface Parameter {
 
   boolean nullable() default true;
 
-  ParameterMode mode() default ParameterMode.In;
-
   int maxLenght() default Integer.MAX_VALUE;
 
   int precision() default 0;

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/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
new file mode 100644
index 0000000..7c74ac3
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/AbstractInvocationHandler.java
@@ -0,0 +1,243 @@
+/*
+ * 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.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Proxy;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+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.commons.api.domain.CommonODataEntity;
+import org.apache.olingo.commons.api.domain.CommonODataEntitySet;
+import org.apache.olingo.commons.api.domain.CommonODataProperty;
+import org.apache.olingo.commons.api.domain.ODataInvokeResult;
+import org.apache.olingo.commons.api.domain.ODataValue;
+import org.apache.olingo.commons.api.edm.EdmOperation;
+import org.apache.olingo.commons.core.edm.EdmTypeInfo;
+import org.apache.olingo.ext.proxy.EntityContainerFactory;
+import org.apache.olingo.ext.proxy.api.OperationType;
+import org.apache.olingo.ext.proxy.api.annotations.Operation;
+import org.apache.olingo.ext.proxy.api.annotations.Parameter;
+import org.apache.olingo.ext.proxy.utils.ClassUtils;
+import org.apache.olingo.ext.proxy.utils.EngineUtils;
+
+abstract class AbstractInvocationHandler<C extends CommonEdmEnabledODataClient<?>> implements InvocationHandler {
+
+  private static final long serialVersionUID = 358520026931462958L;
+
+  protected final C client;
+
+  protected EntityContainerInvocationHandler<C> containerHandler;
+
+  protected AbstractInvocationHandler(
+          final C client, final EntityContainerInvocationHandler<C> containerHandler) {
+
+    this.client = client;
+    this.containerHandler = containerHandler;
+  }
+
+  protected C getClient() {
+    return client;
+  }
+
+  protected boolean isSelfMethod(final Method method, final Object[] args) {
+    final Method[] selfMethods = getClass().getMethods();
+
+    boolean result = false;
+
+    for (int i = 0; i < selfMethods.length && !result; i++) {
+      result = method.getName().equals(selfMethods[i].getName())
+              && Arrays.equals(method.getParameterTypes(), selfMethods[i].getParameterTypes());
+    }
+
+    return result;
+  }
+
+  protected Object invokeSelfMethod(final Method method, final Object[] args)
+          throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+    return getClass().getMethod(method.getName(), method.getParameterTypes()).invoke(this, args);
+  }
+
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  protected Object getEntityCollection(
+          final Class<?> typeRef,
+          final Class<?> typeCollectionRef,
+          final String entityContainerName,
+          final CommonODataEntitySet entitySet,
+          final URI uri,
+          final boolean checkInTheContext) {
+
+    final List<Object> items = new ArrayList<Object>();
+
+    for (CommonODataEntity entityFromSet : entitySet.getEntities()) {
+      items.add(getEntityProxy(
+              entityFromSet, entityContainerName, null, typeRef, checkInTheContext));
+    }
+
+    return Proxy.newProxyInstance(
+            Thread.currentThread().getContextClassLoader(),
+            new Class<?>[] {typeCollectionRef},
+            new EntityCollectionInvocationHandler(containerHandler, items, typeRef, entityContainerName, uri));
+  }
+
+  protected <T> T getEntityProxy(
+          final CommonODataEntity entity,
+          final String entityContainerName,
+          final String entitySetName,
+          final Class<?> type,
+          final boolean checkInTheContext) {
+
+    return getEntityProxy(entity, entityContainerName, entitySetName, type, null, checkInTheContext);
+  }
+
+  @SuppressWarnings({"unchecked"})
+  protected <T> T getEntityProxy(
+          final CommonODataEntity entity,
+          final String entityContainerName,
+          final String entitySetName,
+          final Class<?> type,
+          final String eTag,
+          final boolean checkInTheContext) {
+
+    EntityTypeInvocationHandler<C> handler = (EntityTypeInvocationHandler<C>) EntityTypeInvocationHandler.getInstance(
+            entity, entitySetName, type, containerHandler);
+
+    if (StringUtils.isNotBlank(eTag)) {
+      // override ETag into the wrapped object.
+      handler.setETag(eTag);
+    }
+
+    if (checkInTheContext && EntityContainerFactory.getContext().entityContext().isAttached(handler)) {
+      handler = (EntityTypeInvocationHandler<C>) EntityContainerFactory.getContext().entityContext().
+              getEntity(handler.getUUID());
+    }
+
+    return (T) Proxy.newProxyInstance(
+            Thread.currentThread().getContextClassLoader(),
+            new Class<?>[] {type},
+            handler);
+  }
+
+  protected Object invokeOperation(
+          final Operation annotation,
+          final Method method,
+          final LinkedHashMap<Parameter, Object> parameters,
+          final URI target,
+          final EdmOperation edmOperation)
+          throws InstantiationException, IllegalAccessException, NoSuchMethodException,
+          IllegalArgumentException, InvocationTargetException {
+
+    // 1. invoke params (if present)
+    final Map<String, ODataValue> parameterValues = new HashMap<String, ODataValue>();
+    if (!parameters.isEmpty()) {
+      for (Map.Entry<Parameter, Object> parameter : parameters.entrySet()) {
+
+        if (!parameter.getKey().nullable() && parameter.getValue() == null) {
+          throw new IllegalArgumentException(
+                  "Parameter " + parameter.getKey().name() + " is not nullable but a null value was provided");
+        }
+
+        final EdmTypeInfo type = new EdmTypeInfo.Builder().
+                setEdm(client.getCachedEdm()).setTypeExpression(parameter.getKey().type()).build();
+
+        final ODataValue paramValue = parameter.getValue() == null
+                ? null
+                : EngineUtils.getODataValue(client, type, parameter.getValue());
+
+        parameterValues.put(parameter.getKey().name(), paramValue);
+      }
+    }
+
+    // 2. IMPORTANT: flush any pending change *before* invoke if this operation is side effecting
+    if (annotation.type() == OperationType.ACTION) {
+      new Container(client, containerHandler.getFactory()).flush();
+    }
+
+    // 3. invoke
+    final ODataInvokeResult result = client.getInvokeRequestFactory().getInvokeRequest(
+            target, edmOperation, parameterValues).execute().getBody();
+
+    // 4. process invoke result
+    if (StringUtils.isBlank(annotation.returnType())) {
+      return ClassUtils.returnVoid();
+    }
+
+    final EdmTypeInfo edmType = new EdmTypeInfo.Builder().
+            setEdm(client.getCachedEdm()).setTypeExpression(annotation.returnType()).build();
+
+    if (edmType.isEnumType()) {
+      throw new UnsupportedOperationException("Usupported enum type " + edmType.getFullQualifiedName());
+    }
+
+    if (edmType.isPrimitiveType() || edmType.isComplexType()) {
+      return EngineUtils.getValueFromProperty(
+              client.getCachedEdm(), (CommonODataProperty) result, method.getGenericReturnType());
+    }
+    if (edmType.isEntityType()) {
+      if (edmType.isCollection()) {
+        final ParameterizedType collType = (ParameterizedType) method.getReturnType().getGenericInterfaces()[0];
+        final Class<?> collItemType = (Class<?>) collType.getActualTypeArguments()[0];
+        return getEntityCollection(
+                collItemType,
+                method.getReturnType(),
+                null,
+                (CommonODataEntitySet) result,
+                target,
+                false);
+      } else {
+        return getEntityProxy(
+                (CommonODataEntity) result,
+                null,
+                null,
+                method.getReturnType(),
+                false);
+      }
+    }
+
+    throw new IllegalArgumentException("Could not process the functionImport information");
+  }
+
+  @Override
+  public boolean equals(final Object obj) {
+    return EqualsBuilder.reflectionEquals(this, obj);
+  }
+
+  @Override
+  public int hashCode() {
+    return HashCodeBuilder.reflectionHashCode(this);
+  }
+
+  @Override
+  public String toString() {
+    return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/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
new file mode 100644
index 0000000..0b78fe1
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/CompoundKeyElementWrapper.java
@@ -0,0 +1,57 @@
+/**
+ * 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/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/Container.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/Container.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/Container.java
new file mode 100644
index 0000000..8b4422b
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/Container.java
@@ -0,0 +1,544 @@
+/*
+ * 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.CommonEdmEnabledODataClient;
+import org.apache.olingo.client.api.communication.header.ODataPreferences;
+import org.apache.olingo.client.api.communication.request.ODataStreamedRequest;
+import org.apache.olingo.client.api.communication.request.batch.BatchStreamManager;
+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.api.uri.CommonURIBuilder;
+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.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.AbstractContainer;
+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.EngineUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class Container implements AbstractContainer {
+
+  private static final long serialVersionUID = -3320312269235907501L;
+
+  /**
+   * Logger.
+   */
+  private static final Logger LOG = LoggerFactory.getLogger(Container.class);
+
+  private final CommonEdmEnabledODataClient<?> client;
+
+  private final EntityContainerFactory factory;
+
+  Container(final CommonEdmEnabledODataClient<?> client, final EntityContainerFactory factory) {
+    this.client = client;
+    this.factory = factory;
+  }
+
+  /**
+   * Transactional changes commit.
+   */
+  @Override
+  public void flush() {
+    final CommonODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(client.getServiceRoot());
+
+    final BatchStreamManager streamManager = (BatchStreamManager) ((ODataStreamedRequest) request).execute();
+
+    final ODataChangeset changeset = streamManager.addChangeset();
+
+    final TransactionItems items = new TransactionItems();
+    final List<EntityLinkDesc> delayedUpdates = new ArrayList<EntityLinkDesc>();
+
+    int pos = 0;
+
+    for (AttachedEntity attachedEntity : EntityContainerFactory.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();
+
+    if (response.getStatusCode() != 202) {
+      throw new IllegalStateException("Operation failed");
+    }
+
+    final Iterator<ODataBatchResponseItem> iter = response.getBody();
+
+    if (!items.isEmpty()) {
+      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 EntityTypeInvocationHandler<?> handler = items.get(changesetItemId);
+
+        if (handler != null) {
+          if (res instanceof ODataEntityCreateResponse) {
+            LOG.debug("Upgrade created object '{}'", handler);
+            handler.setEntity(((ODataEntityCreateResponse) res).getBody());
+          } else if (res instanceof ODataEntityUpdateResponse) {
+            LOG.debug("Upgrade updated object '{}'", handler);
+            handler.setEntity(((ODataEntityUpdateResponse) res).getBody());
+          }
+        }
+      }
+    }
+
+    EntityContainerFactory.getContext().detachAll();
+  }
+
+  private void batch(
+          final EntityTypeInvocationHandler<?> handler, 
+          final CommonODataEntity entity, 
+          final ODataChangeset changeset) {
+
+    switch (EntityContainerFactory.getContext().entityContext().getStatus(handler)) {
+      case NEW:
+        batchCreate(handler, entity, changeset);
+        break;
+
+      case CHANGED:
+        batchUpdate(handler, entity, changeset);
+        break;
+
+      case DELETED:
+        batchDelete(handler, entity, changeset);
+        break;
+
+      default:
+        if (handler.isChanged()) {
+          batchUpdate(handler, entity, changeset);
+        }
+    }
+  }
+
+  private void batchCreate(
+          final EntityTypeInvocationHandler<?> handler,
+          final CommonODataEntity entity,
+          final ODataChangeset changeset) {
+
+    LOG.debug("Create '{}'", handler);
+
+    final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(factory.getServiceRoot()).
+            appendEntitySetSegment(handler.getEntitySetName());
+    changeset.addRequest(client.getCUDRequestFactory().getEntityCreateRequest(uriBuilder.build(), entity));
+  }
+
+  private void batchUpdateMediaEntity(
+          final EntityTypeInvocationHandler<?> handler,
+          final URI uri,
+          final InputStream input,
+          final ODataChangeset changeset) {
+
+    LOG.debug("Update media entity '{}'", uri);
+
+    final ODataMediaEntityUpdateRequest<?> req =
+            client.getStreamedRequestFactory().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 EntityTypeInvocationHandler<?> handler,
+          final URI uri,
+          final InputStream input,
+          final ODataChangeset changeset) {
+
+    LOG.debug("Update media entity '{}'", uri);
+
+    final ODataStreamUpdateRequest req = client.getStreamedRequestFactory().getStreamUpdateRequest(uri, input);
+
+    if (StringUtils.isNotBlank(handler.getETag())) {
+      req.setIfMatch(handler.getETag());
+    }
+
+    changeset.addRequest(req);
+  }
+
+  private void batchUpdate(
+          final EntityTypeInvocationHandler<?> handler,
+          final CommonODataEntity changes,
+          final ODataChangeset changeset) {
+
+    LOG.debug("Update '{}'", changes.getEditLink());
+
+    final ODataEntityUpdateRequest<CommonODataEntity> req =
+            client.getServiceVersion().compareTo(ODataServiceVersion.V30) <= 0
+            ? ((org.apache.olingo.client.api.v3.EdmEnabledODataClient) client).getCUDRequestFactory().
+            getEntityUpdateRequest(org.apache.olingo.client.api.communication.request.cud.v3.UpdateType.PATCH, changes)
+            : ((org.apache.olingo.client.api.v4.EdmEnabledODataClient) client).getCUDRequestFactory().
+            getEntityUpdateRequest(org.apache.olingo.client.api.communication.request.cud.v4.UpdateType.PATCH, changes);
+
+    req.setPrefer(new ODataPreferences(client.getServiceVersion()).returnContent());
+
+    if (StringUtils.isNotBlank(handler.getETag())) {
+      req.setIfMatch(handler.getETag());
+    }
+
+    changeset.addRequest(req);
+  }
+
+  private void batchUpdate(
+          final EntityTypeInvocationHandler<?> handler,
+          final URI uri,
+          final CommonODataEntity changes,
+          final ODataChangeset changeset) {
+
+    LOG.debug("Update '{}'", uri);
+
+    final ODataEntityUpdateRequest<CommonODataEntity> req =
+            client.getServiceVersion().compareTo(ODataServiceVersion.V30) <= 0
+            ? ((org.apache.olingo.client.api.v3.EdmEnabledODataClient) client).getCUDRequestFactory().
+            getEntityUpdateRequest(
+            uri, org.apache.olingo.client.api.communication.request.cud.v3.UpdateType.PATCH, changes)
+            : ((org.apache.olingo.client.api.v4.EdmEnabledODataClient) client).getCUDRequestFactory().
+            getEntityUpdateRequest(
+            uri, org.apache.olingo.client.api.communication.request.cud.v4.UpdateType.PATCH, changes);
+
+    req.setPrefer(new ODataPreferences(client.getServiceVersion()).returnContent());
+
+    if (StringUtils.isNotBlank(handler.getETag())) {
+      req.setIfMatch(handler.getETag());
+    }
+
+    changeset.addRequest(req);
+  }
+
+  private void batchDelete(
+          final EntityTypeInvocationHandler<?> handler,
+          final CommonODataEntity entity,
+          final ODataChangeset changeset) {
+
+    LOG.debug("Delete '{}'", entity.getEditLink());
+
+    final ODataDeleteRequest req = client.getCUDRequestFactory().getDeleteRequest(URIUtils.getURI(
+            factory.getServiceRoot(), entity.getEditLink().toASCIIString()));
+
+    if (StringUtils.isNotBlank(handler.getETag())) {
+      req.setIfMatch(handler.getETag());
+    }
+
+    changeset.addRequest(req);
+  }
+
+  private int processEntityContext(
+          final EntityTypeInvocationHandler<?> 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 = EntityContainerFactory.getContext().entityContext().
+            getStatus(handler);
+
+    if (AttachedEntityStatus.DELETED != currentStatus) {
+      entity.getProperties().clear();
+      EngineUtils.addProperties(client, handler.getPropertyChanges(), entity);
+    }
+
+    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<EntityTypeInvocationHandler<?>> toBeLinked = new HashSet<EntityTypeInvocationHandler<?>>();
+      final String serviceRoot = factory.getServiceRoot();
+
+      for (Object proxy : type == ODataLinkType.ENTITY_SET_NAVIGATION
+              ? (Collection) property.getValue() : Collections.singleton(property.getValue())) {
+
+        final EntityTypeInvocationHandler<?> target =
+                (EntityTypeInvocationHandler) Proxy.getInvocationHandler(proxy);
+
+        final AttachedEntityStatus status =
+                EntityContainerFactory.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(serviceRoot, 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(serviceRoot, 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));
+      }
+    }
+
+    // insert into the batch
+    LOG.debug("{}: Insert '{}' into the batch", pos, handler);
+    batch(handler, entity, changeset);
+
+    items.put(handler, pos);
+
+    int startingPos = pos;
+
+    if (handler.getEntity().isMediaEntity()) {
+
+      // update media properties
+      if (!handler.getPropertyChanges().isEmpty()) {
+        final URI targetURI = currentStatus == AttachedEntityStatus.NEW
+                ? URI.create("$" + startingPos)
+                : URIUtils.getURI(factory.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.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.getServiceRoot(),
+              EngineUtils.getEditMediaLink(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) {
+    switch (type) {
+      case ENTITY_NAVIGATION:
+        return client.getObjectFactory().newEntityNavigationLink(name, uri);
+
+      case ENTITY_SET_NAVIGATION:
+        return client.getObjectFactory().newEntitySetNavigationLink(name, uri);
+
+      default:
+        throw new IllegalArgumentException("Invalid link type " + type.name());
+    }
+  }
+
+  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 =
+              client.getObjectFactory().newEntity(delayedUpdate.getSource().getEntity().getTypeName());
+
+      AttachedEntityStatus status =
+              EntityContainerFactory.getContext().entityContext().getStatus(delayedUpdate.getSource());
+
+      final URI sourceURI;
+      if (status == AttachedEntityStatus.CHANGED) {
+        sourceURI = URIUtils.getURI(
+                factory.getServiceRoot(),
+                delayedUpdate.getSource().getEntity().getEditLink().toASCIIString());
+      } else {
+        int sourcePos = items.get(delayedUpdate.getSource());
+        sourceURI = URI.create("$" + sourcePos);
+      }
+
+      for (EntityTypeInvocationHandler<?> target : delayedUpdate.getTargets()) {
+        status = EntityContainerFactory.getContext().entityContext().getStatus(target);
+
+        final URI targetURI;
+        if (status == AttachedEntityStatus.CHANGED) {
+          targetURI = URIUtils.getURI(
+                  factory.getServiceRoot(), target.getEntity().getEditLink().toASCIIString());
+        } else {
+          int targetPos = items.get(target);
+          targetURI = URI.create("$" + targetPos);
+        }
+
+        changes.addLink(delayedUpdate.getType() == ODataLinkType.ENTITY_NAVIGATION
+                ? client.getObjectFactory().newEntityNavigationLink(delayedUpdate.getSourceName(), targetURI)
+                : client.getObjectFactory().newEntitySetNavigationLink(delayedUpdate.getSourceName(), targetURI));
+
+        LOG.debug("'{}' from {} to {}", new Object[] {
+          delayedUpdate.getType().name(), sourceURI, targetURI});
+      }
+
+      batchUpdate(delayedUpdate.getSource(), sourceURI, changes, changeset);
+    }
+  }
+
+  private class TransactionItems {
+
+    private final List<EntityTypeInvocationHandler<?>> keys = new ArrayList<EntityTypeInvocationHandler<?>>();
+
+    private final List<Integer> values = new ArrayList<Integer>();
+
+    public EntityTypeInvocationHandler<?> get(final Integer value) {
+      if (value != null && values.contains(value)) {
+        return keys.get(values.indexOf(value));
+      } else {
+        return null;
+      }
+    }
+
+    public Integer get(final EntityTypeInvocationHandler<?> key) {
+      if (key != null && keys.contains(key)) {
+        return values.get(keys.indexOf(key));
+      } else {
+        return null;
+      }
+    }
+
+    public void remove(final EntityTypeInvocationHandler<?> key) {
+      if (keys.contains(key)) {
+        values.remove(keys.indexOf(key));
+        keys.remove(key);
+      }
+    }
+
+    public void put(final EntityTypeInvocationHandler<?> 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 EntityTypeInvocationHandler<?> 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/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityCollectionInvocationHandler.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityCollectionInvocationHandler.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityCollectionInvocationHandler.java
new file mode 100644
index 0000000..e75056a
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityCollectionInvocationHandler.java
@@ -0,0 +1,140 @@
+/**
+ * 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.Serializable;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.URI;
+import java.util.Collection;
+import java.util.Iterator;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.olingo.client.api.CommonEdmEnabledODataClient;
+import org.apache.olingo.ext.proxy.api.AbstractEntityCollection;
+
+public class EntityCollectionInvocationHandler<T extends Serializable, C extends CommonEdmEnabledODataClient<?>>
+        extends AbstractInvocationHandler<C> implements AbstractEntityCollection<T> {
+
+  private static final long serialVersionUID = 98078202642671726L;
+
+  private final Collection<T> items;
+
+  private final Class<?> itemRef;
+
+  private final URI uri;
+
+  public EntityCollectionInvocationHandler(final EntityContainerInvocationHandler<C> containerHandler,
+          final Collection<T> items, final Class<?> itemRef, final String entityContainerName) {
+
+    this(containerHandler, items, itemRef, entityContainerName, null);
+  }
+
+  public EntityCollectionInvocationHandler(final EntityContainerInvocationHandler<C> containerHandler,
+          final Collection<T> items, final Class<?> itemRef, final String entityContainerName, final URI uri) {
+
+    super(containerHandler.getClient(), containerHandler);
+
+    this.items = items;
+    this.itemRef = itemRef;
+    this.uri = uri;
+  }
+
+  public Class<?> getEntityReference() {
+    return itemRef;
+  }
+
+  public URI getURI() {
+    return uri;
+  }
+
+  @Override
+  public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+    if (isSelfMethod(method, args)) {
+      return invokeSelfMethod(method, args);
+    } else if ("operations".equals(method.getName()) && ArrayUtils.isEmpty(args)) {
+      final Class<?> returnType = method.getReturnType();
+
+      return Proxy.newProxyInstance(
+              Thread.currentThread().getContextClassLoader(),
+              new Class<?>[] {returnType},
+              OperationInvocationHandler.getInstance(this));
+    } else {
+      throw new UnsupportedOperationException("Method not found: " + method);
+    }
+  }
+
+  @Override
+  public int size() {
+    return items.size();
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return items.isEmpty();
+  }
+
+  @Override
+  public boolean contains(final Object object) {
+    return items.contains(object);
+  }
+
+  @Override
+  public Iterator<T> iterator() {
+    return items.iterator();
+  }
+
+  @Override
+  public Object[] toArray() {
+    return items.toArray();
+  }
+
+  @Override
+  public <T> T[] toArray(final T[] array) {
+    return items.toArray(array);
+  }
+
+  @Override
+  public boolean add(final T element) {
+    return items.add(element);
+  }
+
+  @Override
+  public boolean remove(final Object object) {
+    return items.remove(object);
+  }
+
+  @Override
+  public boolean containsAll(final Collection<?> collection) {
+    return items.containsAll(collection);
+  }
+
+  @Override
+  public boolean addAll(final Collection<? extends T> collection) {
+    return items.addAll(collection);
+  }
+
+  @Override
+  public boolean removeAll(final Collection<?> collection) {
+    return items.removeAll(collection);
+  }
+
+  @Override
+  public boolean retainAll(final Collection<?> collection) {
+    return items.retainAll(collection);
+  }
+
+  @Override
+  public void clear() {
+    items.clear();
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/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
new file mode 100644
index 0000000..b74e91f
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityContainerInvocationHandler.java
@@ -0,0 +1,109 @@
+/*
+ * 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.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.olingo.client.api.CommonEdmEnabledODataClient;
+import org.apache.olingo.ext.proxy.EntityContainerFactory;
+import org.apache.olingo.ext.proxy.api.annotations.EntityContainer;
+import org.apache.olingo.ext.proxy.utils.ClassUtils;
+
+public class EntityContainerInvocationHandler<C extends CommonEdmEnabledODataClient<?>>
+        extends AbstractInvocationHandler<C> {
+
+  private static final long serialVersionUID = 7379006755693410764L;
+
+  private final EntityContainerFactory factory;
+
+  protected final String namespace;
+
+  private final String name;
+
+  private final boolean defaultEC;
+
+  public static <C extends CommonEdmEnabledODataClient<?>> EntityContainerInvocationHandler<C> getInstance(
+          final C client, final Class<?> ref, final EntityContainerFactory factory) {
+
+    final EntityContainerInvocationHandler<C> instance = new EntityContainerInvocationHandler<C>(client, ref, factory);
+    instance.containerHandler = instance;
+    return instance;
+  }
+
+  private EntityContainerInvocationHandler(
+          final C client, final Class<?> ref, final EntityContainerFactory factory) {
+
+    super(client, null);
+
+    final Annotation annotation = ref.getAnnotation(EntityContainer.class);
+    if (!(annotation instanceof EntityContainer)) {
+      throw new IllegalArgumentException(
+              ref.getName() + " is not annotated as @" + EntityContainer.class.getSimpleName());
+    }
+
+    this.factory = factory;
+    this.name = ((EntityContainer) annotation).name();
+    this.defaultEC = ((EntityContainer) annotation).isDefaultEntityContainer();
+    this.namespace = ((EntityContainer) annotation).namespace();
+  }
+
+  EntityContainerFactory getFactory() {
+    return factory;
+  }
+
+  boolean isDefaultEntityContainer() {
+    return defaultEC;
+  }
+
+  String getEntityContainerName() {
+    return name;
+  }
+
+  String getSchemaName() {
+    return namespace;
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+    if (isSelfMethod(method, args)) {
+      return invokeSelfMethod(method, args);
+    } else if ("flush".equals(method.getName()) && ArrayUtils.isEmpty(args)) {
+      new Container(client, factory).flush();
+      return ClassUtils.returnVoid();
+    } else if ("operations".equals(method.getName()) && ArrayUtils.isEmpty(args)) {
+      final Class<?> returnType = method.getReturnType();
+
+      return Proxy.newProxyInstance(
+              Thread.currentThread().getContextClassLoader(),
+              new Class<?>[] {returnType},
+              OperationInvocationHandler.getInstance(this));
+    } else {
+      final Class<?> returnType = method.getReturnType();
+
+      return Proxy.newProxyInstance(
+              Thread.currentThread().getContextClassLoader(),
+              new Class<?>[] {returnType},
+              EntitySetInvocationHandler.getInstance(returnType, this));
+
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/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
new file mode 100644
index 0000000..b800b31
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntitySetInvocationHandler.java
@@ -0,0 +1,400 @@
+/*
+ * 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.Serializable;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+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.ArrayUtils;
+import org.apache.olingo.client.api.CommonEdmEnabledODataClient;
+import org.apache.olingo.client.api.communication.request.retrieve.ODataValueRequest;
+import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse;
+import org.apache.olingo.client.api.uri.CommonURIBuilder;
+import org.apache.olingo.commons.api.domain.CommonODataEntity;
+import org.apache.olingo.commons.api.domain.CommonODataEntitySet;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.commons.api.format.ODataValueFormat;
+import org.apache.olingo.ext.proxy.EntityContainerFactory;
+import org.apache.olingo.ext.proxy.api.AbstractEntityCollection;
+import org.apache.olingo.ext.proxy.api.AbstractEntitySet;
+import org.apache.olingo.ext.proxy.api.Query;
+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.EntitySet;
+import org.apache.olingo.ext.proxy.api.annotations.EntityType;
+import org.apache.olingo.ext.proxy.context.AttachedEntityStatus;
+import org.apache.olingo.ext.proxy.context.EntityContext;
+import org.apache.olingo.ext.proxy.context.EntityUUID;
+import org.apache.olingo.ext.proxy.utils.ClassUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class EntitySetInvocationHandler<C extends CommonEdmEnabledODataClient<?>,
+        T extends Serializable, KEY extends Serializable, EC extends AbstractEntityCollection<T>>
+        extends AbstractInvocationHandler
+        implements AbstractEntitySet<T, KEY, EC> {
+
+  private static final long serialVersionUID = 2629912294765040027L;
+
+  /**
+   * Logger.
+   */
+  private static final Logger LOG = LoggerFactory.getLogger(EntitySetInvocationHandler.class);
+
+  private final Class<T> typeRef;
+
+  private final Class<EC> collTypeRef;
+
+  private final String entitySetName;
+
+  private final URI uri;
+
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  static EntitySetInvocationHandler getInstance(
+          final Class<?> ref, final EntityContainerInvocationHandler containerHandler) {
+
+    return new EntitySetInvocationHandler(ref, containerHandler);
+  }
+
+  @SuppressWarnings("unchecked")
+  private EntitySetInvocationHandler(
+          final Class<?> ref,
+          final EntityContainerInvocationHandler<?> containerHandler) {
+
+    super(containerHandler.getClient(), containerHandler);
+
+    final Annotation annotation = ref.getAnnotation(EntitySet.class);
+    if (!(annotation instanceof EntitySet)) {
+      throw new IllegalArgumentException("Return type " + ref.getName()
+              + " is not annotated as @" + EntitySet.class.getSimpleName());
+    }
+
+    this.entitySetName = ((EntitySet) annotation).name();
+
+    final Type[] abstractEntitySetParams =
+            ((ParameterizedType) ref.getGenericInterfaces()[0]).getActualTypeArguments();
+
+    this.typeRef = (Class<T>) abstractEntitySetParams[0];
+    if (typeRef.getAnnotation(EntityType.class) == null) {
+      throw new IllegalArgumentException("Invalid entity '" + typeRef.getSimpleName() + "'");
+    }
+    this.collTypeRef = (Class<EC>) abstractEntitySetParams[2];
+
+    final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(containerHandler.getFactory().getServiceRoot());
+
+    final StringBuilder entitySetSegment = new StringBuilder();
+    if (!containerHandler.isDefaultEntityContainer()) {
+      entitySetSegment.append(containerHandler.getEntityContainerName()).append('.');
+    }
+    entitySetSegment.append(entitySetName);
+
+    uriBuilder.appendEntitySetSegment(entitySetSegment.toString());
+    this.uri = uriBuilder.build();
+  }
+
+  Class<T> getTypeRef() {
+    return typeRef;
+  }
+
+  Class<EC> getCollTypeRef() {
+    return collTypeRef;
+  }
+
+  String getEntitySetName() {
+    return entitySetName;
+  }
+
+  URI getUri() {
+    return uri;
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+    if (isSelfMethod(method, args)) {
+      return invokeSelfMethod(method, args);
+    } else if (method.getName().startsWith("new") && ArrayUtils.isEmpty(args)) {
+      if (method.getName().endsWith("Collection")) {
+        return newEntityCollection(method.getReturnType());
+      } else {
+        return newEntity(method.getReturnType());
+      }
+    } else {
+      throw new UnsupportedOperationException("Method not found: " + method);
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private <NE> NE newEntity(final Class<NE> reference) {
+    final CommonODataEntity entity = client.getObjectFactory().newEntity(
+            new FullQualifiedName(containerHandler.getSchemaName(), ClassUtils.getEntityTypeName(reference)));
+
+    final EntityTypeInvocationHandler<?> handler =
+            EntityTypeInvocationHandler.getInstance(entity, entitySetName, reference, containerHandler);
+    EntityContainerFactory.getContext().entityContext().attachNew(handler);
+
+    return (NE) Proxy.newProxyInstance(
+            Thread.currentThread().getContextClassLoader(),
+            new Class<?>[] {reference},
+            handler);
+  }
+
+  @SuppressWarnings("unchecked")
+  private <NEC> NEC newEntityCollection(final Class<NEC> reference) {
+    return (NEC) Proxy.newProxyInstance(
+            Thread.currentThread().getContextClassLoader(),
+            new Class<?>[] {reference},
+            new EntityCollectionInvocationHandler<T, C>(
+            containerHandler, new ArrayList<T>(), typeRef, containerHandler.getEntityContainerName()));
+  }
+
+  @Override
+  public Long count() {
+    final ODataValueRequest req = client.getRetrieveRequestFactory().
+            getValueRequest(client.getURIBuilder(this.uri.toASCIIString()).count().build());
+    req.setFormat(ODataValueFormat.TEXT);
+    return Long.valueOf(req.execute().getBody().asPrimitive().toString());
+  }
+
+  @Override
+  public Boolean exists(final KEY key) throws IllegalArgumentException {
+    boolean result = false;
+
+    try {
+      result = get(key) != null;
+    } catch (Exception e) {
+      LOG.error("Could not check existence of {}({})", this.entitySetName, key, e);
+    }
+
+    return result;
+  }
+
+  private LinkedHashMap<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
+  public T get(KEY key) throws IllegalArgumentException {
+    return get(key, typeRef);
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public <S extends T> S get(final KEY key, final Class<S> typeRef) throws IllegalArgumentException {
+    if (key == null) {
+      throw new IllegalArgumentException("Null key");
+    }
+
+    final EntityUUID uuid = new EntityUUID(
+            containerHandler.getEntityContainerName(),
+            entitySetName,
+            new FullQualifiedName(ClassUtils.getNamespace(typeRef), ClassUtils.getEntityTypeName(typeRef)),
+            key);
+
+    LOG.debug("Ask for '{}({})'", typeRef.getSimpleName(), key);
+
+    EntityTypeInvocationHandler<?> handler =
+            EntityContainerFactory.getContext().entityContext().getEntity(uuid);
+
+    if (handler == null) {
+      // not yet attached: search against the service
+      try {
+        LOG.debug("Search for '{}({})' into the service", typeRef.getSimpleName(), key);
+        final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(this.uri.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));
+        }
+
+        LOG.debug("Execute query '{}'", uriBuilder.toString());
+
+        final ODataRetrieveResponse<CommonODataEntity> res =
+                client.getRetrieveRequestFactory().getEntityRequest(uriBuilder.build()).execute();
+
+        handler = EntityTypeInvocationHandler.getInstance(res.getBody(), this, typeRef);
+        handler.setETag(res.getETag());
+      } catch (Exception e) {
+        LOG.info("Entity '" + uuid + "' not found", e);
+      }
+    } else if (isDeleted(handler)) {
+      // object deleted
+      LOG.debug("Object '{}({})' has been delete", typeRef.getSimpleName(), uuid);
+      handler = null;
+    }
+
+    return handler == null ? null : (S) Proxy.newProxyInstance(
+            Thread.currentThread().getContextClassLoader(),
+            new Class<?>[] {typeRef},
+            handler);
+  }
+
+  @SuppressWarnings("unchecked")
+  public <S extends T> Map.Entry<List<S>, URI> fetchPartialEntitySet(final URI uri, final Class<S> typeRef) {
+    final ODataRetrieveResponse<CommonODataEntitySet> res =
+            client.getRetrieveRequestFactory().getEntitySetRequest(uri).execute();
+
+    final CommonODataEntitySet entitySet = res.getBody();
+
+    final List<S> items = new ArrayList<S>(entitySet.getEntities().size());
+    for (CommonODataEntity entity : entitySet.getEntities()) {
+      final EntityTypeInvocationHandler<?> handler = EntityTypeInvocationHandler.getInstance(entity, this, typeRef);
+
+      final EntityTypeInvocationHandler<?> handlerInTheContext =
+              EntityContainerFactory.getContext().entityContext().getEntity(handler.getUUID());
+
+      items.add((S) Proxy.newProxyInstance(
+              Thread.currentThread().getContextClassLoader(),
+              new Class<?>[] {typeRef},
+              handlerInTheContext == null ? handler : handlerInTheContext));
+    }
+
+    return new AbstractMap.SimpleEntry<List<S>, URI>(items, entitySet.getNext());
+  }
+
+  @SuppressWarnings("unchecked")
+  public <S extends T, SEC extends AbstractEntityCollection<S>> SEC fetchWholeEntitySet(
+          final URI entitySetURI, final Class<S> typeRef, final Class<SEC> collTypeRef) {
+
+    final List<S> items = new ArrayList<S>();
+
+    URI nextURI = entitySetURI;
+    while (nextURI != null) {
+      final Map.Entry<List<S>, URI> entitySet = fetchPartialEntitySet(nextURI, typeRef);
+      nextURI = entitySet.getValue();
+      items.addAll(entitySet.getKey());
+    }
+
+    return (SEC) Proxy.newProxyInstance(
+            Thread.currentThread().getContextClassLoader(),
+            new Class<?>[] {collTypeRef},
+            new EntityCollectionInvocationHandler<S, C>(
+            containerHandler, items, typeRef, containerHandler.getEntityContainerName(), entitySetURI));
+  }
+
+  @Override
+  public EC getAll() {
+    return getAll(collTypeRef);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public <S extends T, SEC extends AbstractEntityCollection<S>> SEC getAll(final Class<SEC> collTypeRef) {
+    final Class<S> typeRef = (Class<S>) ClassUtils.extractTypeArg(collTypeRef);
+
+    final URI entitySetURI = client.getURIBuilder(this.uri.toASCIIString()).appendNavigationSegment(
+            new FullQualifiedName(ClassUtils.getNamespace(typeRef), ClassUtils.getEntityTypeName(typeRef)).toString()).
+            build();
+
+    return fetchWholeEntitySet(entitySetURI, typeRef, collTypeRef);
+  }
+
+  @Override
+  public Query<T, EC> createQuery() {
+    return new QueryImpl<T, EC>(this.client, this.collTypeRef, this.uri, this);
+  }
+
+  @Override
+  public <S extends T, SEC extends AbstractEntityCollection<S>> Query<S, SEC> createQuery(
+          final Class<SEC> reference) {
+
+    return new QueryImpl<S, SEC>(this.client, reference, this.uri, this);
+  }
+
+  @Override
+  public void delete(final KEY key) throws IllegalArgumentException {
+    final EntityContext entityContext = EntityContainerFactory.getContext().entityContext();
+
+    EntityTypeInvocationHandler<?> entity = entityContext.getEntity(new EntityUUID(
+            containerHandler.getEntityContainerName(),
+            entitySetName,
+            new FullQualifiedName(ClassUtils.getNamespace(typeRef), ClassUtils.getEntityTypeName(typeRef)),
+            key));
+
+    if (entity == null) {
+      // search for entity
+      final T searched = get(key);
+      entity = (EntityTypeInvocationHandler<?>) Proxy.getInvocationHandler(searched);
+      entityContext.attach(entity, AttachedEntityStatus.DELETED);
+    } else {
+      entityContext.setStatus(entity, AttachedEntityStatus.DELETED);
+    }
+  }
+
+  @Override
+  public <S extends T> void delete(final Iterable<S> entities) {
+    final EntityContext entityContext = EntityContainerFactory.getContext().entityContext();
+
+    for (T en : entities) {
+      final EntityTypeInvocationHandler<?> entity = (EntityTypeInvocationHandler<?>) Proxy.getInvocationHandler(en);
+      if (entityContext.isAttached(entity)) {
+        entityContext.setStatus(entity, AttachedEntityStatus.DELETED);
+      } else {
+        entityContext.attach(entity, AttachedEntityStatus.DELETED);
+      }
+    }
+  }
+
+  private boolean isDeleted(final EntityTypeInvocationHandler<?> handler) {
+    return EntityContainerFactory.getContext().entityContext().getStatus(handler) == AttachedEntityStatus.DELETED;
+  }
+
+  @Override
+  public EntitySetIterator<T, KEY, EC> iterator() {
+    return new EntitySetIterator<T, KEY, EC>(
+            client.getURIBuilder(this.uri.toASCIIString()).appendNavigationSegment(
+            new FullQualifiedName(ClassUtils.getNamespace(typeRef), ClassUtils.getEntityTypeName(typeRef)).toString()).
+            build(),
+            this);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntitySetIterator.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntitySetIterator.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntitySetIterator.java
new file mode 100644
index 0000000..4054491
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntitySetIterator.java
@@ -0,0 +1,85 @@
+/*
+ * 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.Serializable;
+import java.net.URI;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import org.apache.olingo.ext.proxy.api.AbstractEntityCollection;
+
+class EntitySetIterator<T extends Serializable, KEY extends Serializable, EC extends AbstractEntityCollection<T>>
+        implements Iterator<T> {
+
+  private final EntitySetInvocationHandler<?, T, KEY, EC> esi;
+
+  private URI next;
+
+  private Iterator<T> current;
+
+  EntitySetIterator(final URI uri, EntitySetInvocationHandler<?, T, KEY, EC> esi) {
+    this.esi = esi;
+    this.next = uri;
+    this.current = Collections.<T>emptyList().iterator();
+  }
+
+  @Override
+  public boolean hasNext() {
+    final boolean res;
+    if (this.current.hasNext()) {
+      res = true;
+    } else if (this.next == null) {
+      res = false;
+    } else {
+      goon();
+      res = current.hasNext();
+    }
+    return res;
+  }
+
+  @Override
+  public T next() {
+    T res;
+    try {
+      res = this.current.next();
+    } catch (NoSuchElementException e) {
+      if (this.next == null) {
+        throw e;
+      }
+      goon();
+      res = next();
+    }
+
+    return res;
+  }
+
+  @Override
+  public void remove() {
+    this.current.remove();
+  }
+
+  private void goon() {
+    final Map.Entry<List<T>, URI> entitySet = esi.fetchPartialEntitySet(this.next, this.esi.getTypeRef());
+    this.next = entitySet.getValue();
+    this.current = entitySet.getKey().iterator();
+  }
+}