You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by ch...@apache.org on 2014/05/12 10:38:39 UTC

[04/50] [abbrv] [OLINGO-260] providing proxy merge from ODataJClient. Still missing integration tests

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityTypeInvocationHandler.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityTypeInvocationHandler.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityTypeInvocationHandler.java
new file mode 100644
index 0000000..389f791
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/EntityTypeInvocationHandler.java
@@ -0,0 +1,564 @@
+/*
+ * 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.annotation.Annotation;
+import java.lang.reflect.InvocationHandler;
+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.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.olingo.client.api.CommonEdmEnabledODataClient;
+import org.apache.olingo.client.api.communication.request.retrieve.ODataMediaRequest;
+import org.apache.olingo.client.api.communication.response.ODataRetrieveResponse;
+import org.apache.olingo.client.core.uri.URIUtils;
+import org.apache.olingo.commons.api.domain.CommonODataEntity;
+import org.apache.olingo.commons.api.domain.CommonODataProperty;
+import org.apache.olingo.commons.api.domain.ODataInlineEntity;
+import org.apache.olingo.commons.api.domain.ODataInlineEntitySet;
+import org.apache.olingo.commons.api.domain.ODataLink;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.commons.api.format.ODataMediaFormat;
+import org.apache.olingo.ext.proxy.EntityContainerFactory;
+import org.apache.olingo.ext.proxy.api.AbstractEntityCollection;
+import org.apache.olingo.ext.proxy.api.annotations.EntityType;
+import org.apache.olingo.ext.proxy.api.annotations.NavigationProperty;
+import org.apache.olingo.ext.proxy.api.annotations.Property;
+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.apache.olingo.ext.proxy.utils.EngineUtils;
+
+public class EntityTypeInvocationHandler<C extends CommonEdmEnabledODataClient<?>>
+        extends AbstractInvocationHandler<C> {
+
+  private static final long serialVersionUID = 2629912294765040037L;
+
+  private CommonODataEntity entity;
+
+  private final Class<?> typeRef;
+
+  private Map<String, Object> propertyChanges = new HashMap<String, Object>();
+
+  private Map<String, InputStream> streamedPropertyChanges = new HashMap<String, InputStream>();
+
+  private Map<NavigationProperty, Object> linkChanges = new HashMap<NavigationProperty, Object>();
+
+  private InputStream stream;
+
+  private EntityUUID uuid;
+
+  private final EntityContext entityContext = EntityContainerFactory.getContext().entityContext();
+
+  private int propertiesTag;
+
+  private int linksTag;
+
+  static EntityTypeInvocationHandler<?> getInstance(
+          final CommonODataEntity entity,
+          final EntitySetInvocationHandler<?, ?, ?, ?> entitySet,
+          final Class<?> typeRef) {
+
+    return getInstance(
+            entity,
+            entitySet.getEntitySetName(),
+            typeRef,
+            entitySet.containerHandler);
+  }
+
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  static EntityTypeInvocationHandler<?> getInstance(
+          final CommonODataEntity entity,
+          final String entitySetName,
+          final Class<?> typeRef,
+          final EntityContainerInvocationHandler<?> containerHandler) {
+
+    return new EntityTypeInvocationHandler(
+            entity, entitySetName, typeRef, containerHandler);
+  }
+
+  private EntityTypeInvocationHandler(
+          final CommonODataEntity entity,
+          final String entitySetName,
+          final Class<?> typeRef,
+          final EntityContainerInvocationHandler<C> containerHandler) {
+
+    super(containerHandler.getClient(), containerHandler);
+    this.typeRef = typeRef;
+
+    this.entity = entity;
+    this.entity.setMediaEntity(typeRef.getAnnotation(EntityType.class).hasStream());
+
+    this.uuid = new EntityUUID(
+            containerHandler.getEntityContainerName(),
+            entitySetName,
+            entity.getTypeName(),
+            EngineUtils.getKey(client.getCachedEdm(), typeRef, entity));
+
+    this.stream = null;
+    this.propertiesTag = 0;
+    this.linksTag = 0;
+  }
+
+  public void setEntity(final CommonODataEntity entity) {
+    this.entity = entity;
+    this.entity.setMediaEntity(typeRef.getAnnotation(EntityType.class).hasStream());
+
+    this.uuid = new EntityUUID(
+            getUUID().getContainerName(),
+            getUUID().getEntitySetName(),
+            getUUID().getName(),
+            EngineUtils.getKey(client.getCachedEdm(), typeRef, entity));
+
+    this.propertyChanges.clear();
+    this.linkChanges.clear();
+    this.streamedPropertyChanges.clear();
+    this.propertiesTag = 0;
+    this.linksTag = 0;
+    this.stream = null;
+  }
+
+  public EntityUUID getUUID() {
+    return uuid;
+  }
+
+  public FullQualifiedName getName() {
+    return this.entity.getTypeName();
+  }
+
+  public String getEntityContainerName() {
+    return uuid.getContainerName();
+  }
+
+  public String getEntitySetName() {
+    return uuid.getEntitySetName();
+  }
+
+  public Class<?> getTypeRef() {
+    return typeRef;
+  }
+
+  public CommonODataEntity getEntity() {
+    return entity;
+  }
+
+  public Map<String, Object> getPropertyChanges() {
+    return propertyChanges;
+  }
+
+  public Map<NavigationProperty, Object> getLinkChanges() {
+    return linkChanges;
+  }
+
+  /**
+   * Gets the current ETag defined into the wrapped entity.
+   *
+   * @return
+   */
+  public String getETag() {
+    return this.entity.getETag();
+  }
+
+  /**
+   * Overrides ETag value defined into the wrapped entity.
+   *
+   * @param eTag ETag.
+   */
+  public void setETag(final String eTag) {
+    this.entity.setETag(eTag);
+  }
+
+  @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 ("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 if (method.getName().startsWith("get")) {
+      // Assumption: for each getter will always exist a setter and viceversa.
+      // get method annotation and check if it exists as expected
+      final Object res;
+
+      final Method getter = typeRef.getMethod(method.getName());
+
+      final Property property = ClassUtils.getAnnotation(Property.class, getter);
+      if (property == null) {
+        final NavigationProperty navProp = ClassUtils.getAnnotation(NavigationProperty.class, getter);
+        if (navProp == null) {
+          throw new UnsupportedOperationException("Unsupported method " + method.getName());
+        } else {
+          // if the getter refers to a navigation property ... navigate and follow link if necessary
+          res = getNavigationPropertyValue(navProp, getter);
+        }
+      } else {
+        // if the getter refers to a property .... get property from wrapped entity
+        res = getPropertyValue(property, getter.getGenericReturnType());
+      }
+
+      // attach the current handler
+      attach();
+
+      return res;
+    } else if (method.getName().startsWith("set")) {
+      // get the corresponding getter method (see assumption above)
+      final String getterName = method.getName().replaceFirst("set", "get");
+      final Method getter = typeRef.getMethod(getterName);
+
+      final Property property = ClassUtils.getAnnotation(Property.class, getter);
+      if (property == null) {
+        final NavigationProperty navProp = ClassUtils.getAnnotation(NavigationProperty.class, getter);
+        if (navProp == null) {
+          throw new UnsupportedOperationException("Unsupported method " + method.getName());
+        } else {
+          // if the getter refers to a navigation property ... 
+          if (ArrayUtils.isEmpty(args) || args.length != 1) {
+            throw new IllegalArgumentException("Invalid argument");
+          }
+
+          setNavigationPropertyValue(navProp, args[0]);
+        }
+      } else {
+        setPropertyValue(property, args[0]);
+      }
+
+      return ClassUtils.returnVoid();
+    } else {
+      throw new UnsupportedOperationException("Method not found: " + method);
+    }
+  }
+
+  private Object getNavigationPropertyValue(final NavigationProperty property, final Method getter) {
+    final Class<?> type = getter.getReturnType();
+    final Class<?> collItemType;
+    if (AbstractEntityCollection.class.isAssignableFrom(type)) {
+      final Type[] entityCollectionParams =
+              ((ParameterizedType) type.getGenericInterfaces()[0]).getActualTypeArguments();
+      collItemType = (Class<?>) entityCollectionParams[0];
+    } else {
+      collItemType = type;
+    }
+
+    final Object navPropValue;
+
+    if (linkChanges.containsKey(property)) {
+      navPropValue = linkChanges.get(property);
+    } else {
+      final ODataLink link = EngineUtils.getNavigationLink(property.name(), entity);
+      if (link instanceof ODataInlineEntity) {
+        // return entity
+        navPropValue = getEntityProxy(
+                ((ODataInlineEntity) link).getEntity(),
+                property.targetContainer(),
+                property.targetEntitySet(),
+                type,
+                false);
+      } else if (link instanceof ODataInlineEntitySet) {
+        // return entity set
+        navPropValue = getEntityCollection(
+                collItemType,
+                type,
+                property.targetContainer(),
+                ((ODataInlineEntitySet) link).getEntitySet(),
+                link.getLink(),
+                false);
+      } else {
+        // navigate
+        final URI uri = URIUtils.getURI(
+                containerHandler.getFactory().getServiceRoot(), link.getLink().toASCIIString());
+
+        if (AbstractEntityCollection.class.isAssignableFrom(type)) {
+          navPropValue = getEntityCollection(
+                  collItemType,
+                  type,
+                  property.targetContainer(),
+                  client.getRetrieveRequestFactory().getEntitySetRequest(uri).execute().getBody(),
+                  uri,
+                  true);
+        } else {
+          final ODataRetrieveResponse<CommonODataEntity> res =
+                  client.getRetrieveRequestFactory().getEntityRequest(uri).execute();
+
+          navPropValue = getEntityProxy(
+                  res.getBody(),
+                  property.targetContainer(),
+                  property.targetEntitySet(),
+                  type,
+                  res.getETag(),
+                  true);
+        }
+      }
+
+      if (navPropValue != null) {
+        int checkpoint = linkChanges.hashCode();
+        linkChanges.put(property, navPropValue);
+        updateLinksTag(checkpoint);
+      }
+    }
+
+    return navPropValue;
+  }
+
+  private Object getPropertyValue(final String name, final Type type) {
+    try {
+      final Object res;
+
+      if (propertyChanges.containsKey(name)) {
+        res = propertyChanges.get(name);
+      } else {
+
+        res = type == null
+                ? EngineUtils.getValueFromProperty(
+                client.getCachedEdm(), entity.getProperty(name))
+                : EngineUtils.getValueFromProperty(
+                client.getCachedEdm(), entity.getProperty(name), type);
+
+        if (res != null) {
+          int checkpoint = propertyChanges.hashCode();
+          propertyChanges.put(name, res);
+          updatePropertiesTag(checkpoint);
+        }
+      }
+
+      return res;
+    } catch (Exception e) {
+      throw new IllegalArgumentException("Error getting value for property '" + name + "'", e);
+    }
+  }
+
+  private Object getPropertyValue(final Property property, final Type type) {
+    if (!(type instanceof ParameterizedType) && (Class<?>) type == InputStream.class) {
+      return getStreamedProperty(property);
+    } else {
+      return getPropertyValue(property.name(), type);
+    }
+  }
+
+  public Object getAdditionalProperty(final String name) {
+    return getPropertyValue(name, null);
+  }
+
+  public Collection<String> getAdditionalPropertyNames() {
+    final Set<String> res = new HashSet<String>(propertyChanges.keySet());
+    final Set<String> propertyNames = new HashSet<String>();
+    for (Method method : typeRef.getMethods()) {
+      final Annotation ann = method.getAnnotation(Property.class);
+      if (ann != null) {
+        final String property = ((Property) ann).name();
+        propertyNames.add(property);
+
+        // maybe someone could add a normal attribute to the additional set
+        res.remove(property);
+      }
+    }
+
+    for (CommonODataProperty property : entity.getProperties()) {
+      if (!propertyNames.contains(property.getName())) {
+        res.add(property.getName());
+      }
+    }
+
+    return res;
+  }
+
+  private void setNavigationPropertyValue(final NavigationProperty property, final Object value) {
+    // 1) attach source entity
+    attach(AttachedEntityStatus.CHANGED, false);
+
+    // 2) attach the target entity handlers
+    for (Object link : AbstractEntityCollection.class.isAssignableFrom(value.getClass())
+            ? (AbstractEntityCollection) value : Collections.singleton(value)) {
+
+      final InvocationHandler etih = Proxy.getInvocationHandler(link);
+      if (!(etih instanceof EntityTypeInvocationHandler)) {
+        throw new IllegalArgumentException("Invalid argument type");
+      }
+
+      @SuppressWarnings("unchecked")
+      final EntityTypeInvocationHandler<C> handler = (EntityTypeInvocationHandler<C>) etih;
+      if (!handler.getTypeRef().isAnnotationPresent(EntityType.class)) {
+        throw new IllegalArgumentException(
+                "Invalid argument type " + handler.getTypeRef().getSimpleName());
+      }
+
+      if (!entityContext.isAttached(handler)) {
+        entityContext.attach(handler, AttachedEntityStatus.LINKED);
+      }
+    }
+
+    // 3) add links
+    linkChanges.put(property, value);
+  }
+
+  private void setPropertyValue(final Property property, final Object value) {
+    if (property.type().equalsIgnoreCase("Edm.Stream")) {
+      setStreamedProperty(property, (InputStream) value);
+    } else {
+      propertyChanges.put(property.name(), value);
+    }
+
+    attach(AttachedEntityStatus.CHANGED);
+  }
+
+  public void addAdditionalProperty(final String name, final Object value) {
+    propertyChanges.put(name, value);
+    attach(AttachedEntityStatus.CHANGED);
+  }
+
+  private void updatePropertiesTag(final int checkpoint) {
+    if (checkpoint == propertiesTag) {
+      propertiesTag = propertyChanges.hashCode();
+    }
+  }
+
+  private void updateLinksTag(final int checkpoint) {
+    if (checkpoint == linksTag) {
+      linksTag = linkChanges.hashCode();
+    }
+  }
+
+  public boolean isChanged() {
+    return this.linkChanges.hashCode() != this.linksTag
+            || this.propertyChanges.hashCode() != this.propertiesTag
+            || this.stream != null
+            || !this.streamedPropertyChanges.isEmpty();
+  }
+
+  public void setStream(final InputStream stream) {
+    if (typeRef.getAnnotation(EntityType.class).hasStream()) {
+      IOUtils.closeQuietly(this.stream);
+      this.stream = stream;
+      attach(AttachedEntityStatus.CHANGED);
+    }
+  }
+
+  public InputStream getStreamChanges() {
+    return this.stream;
+  }
+
+  public Map<String, InputStream> getStreamedPropertyChanges() {
+    return streamedPropertyChanges;
+  }
+
+  public InputStream getStream() {
+
+    final String contentSource = entity.getMediaContentSource();
+
+    if (this.stream == null
+            && typeRef.getAnnotation(EntityType.class).hasStream()
+            && StringUtils.isNotBlank(contentSource)) {
+
+      final String comntentType =
+              StringUtils.isBlank(entity.getMediaContentType()) ? "*/*" : entity.getMediaContentType();
+
+      final URI contentSourceURI = URIUtils.getURI(containerHandler.getFactory().getServiceRoot(), contentSource);
+
+      final ODataMediaRequest retrieveReq = client.getRetrieveRequestFactory().getMediaRequest(contentSourceURI);
+      retrieveReq.setFormat(ODataMediaFormat.fromFormat(comntentType));
+
+      this.stream = retrieveReq.execute().getBody();
+    }
+
+    return this.stream;
+  }
+
+  public Object getStreamedProperty(final Property property) {
+
+    InputStream res = streamedPropertyChanges.get(property.name());
+
+    try {
+      if (res == null) {
+        final URI link = URIUtils.getURI(
+                containerHandler.getFactory().getServiceRoot(),
+                EngineUtils.getEditMediaLink(property.name(), this.entity).toASCIIString());
+
+        final ODataMediaRequest req = client.getRetrieveRequestFactory().getMediaRequest(link);
+        res = req.execute().getBody();
+
+      }
+    } catch (Exception e) {
+      res = null;
+    }
+
+    return res;
+
+  }
+
+  private void setStreamedProperty(final Property property, final InputStream input) {
+    final Object obj = propertyChanges.get(property.name());
+    if (obj != null && obj instanceof InputStream) {
+      IOUtils.closeQuietly((InputStream) obj);
+    }
+
+    streamedPropertyChanges.put(property.name(), input);
+  }
+
+  private void attach() {
+    if (!entityContext.isAttached(this)) {
+      entityContext.attach(this, AttachedEntityStatus.ATTACHED);
+    }
+  }
+
+  private void attach(final AttachedEntityStatus status) {
+    attach(status, true);
+  }
+
+  private void attach(final AttachedEntityStatus status, final boolean override) {
+    if (entityContext.isAttached(this)) {
+      if (override) {
+        entityContext.setStatus(this, status);
+      }
+    } else {
+      entityContext.attach(this, status);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return uuid.toString();
+  }
+
+  @Override
+  public int hashCode() {
+    return uuid.hashCode();
+  }
+
+  @Override
+  public boolean equals(final Object obj) {
+    return obj instanceof EntityTypeInvocationHandler
+            && ((EntityTypeInvocationHandler) obj).getUUID().equals(uuid);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/OperationInvocationHandler.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/OperationInvocationHandler.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/OperationInvocationHandler.java
new file mode 100644
index 0000000..0f61208
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/OperationInvocationHandler.java
@@ -0,0 +1,218 @@
+/*
+ * 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.InvocationHandler;
+import java.lang.reflect.Method;
+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 org.apache.olingo.client.api.CommonEdmEnabledODataClient;
+import org.apache.olingo.client.api.uri.CommonURIBuilder;
+import org.apache.olingo.client.core.uri.URIUtils;
+import org.apache.olingo.commons.api.domain.CommonODataEntity;
+import org.apache.olingo.commons.api.domain.ODataOperation;
+import org.apache.olingo.commons.api.edm.Edm;
+import org.apache.olingo.commons.api.edm.EdmEntityContainer;
+import org.apache.olingo.commons.api.edm.EdmOperation;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.ext.proxy.api.OperationExecutor;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class OperationInvocationHandler<C extends CommonEdmEnabledODataClient<?>> extends AbstractInvocationHandler<C>
+        implements OperationExecutor {
+
+  private static final long serialVersionUID = 2629912294765040027L;
+
+  private final InvocationHandler target;
+
+  private final FullQualifiedName targetFQN;
+
+  private final String serviceRoot;
+
+  /**
+   * Logger.
+   */
+  private static final Logger LOG = LoggerFactory.getLogger(OperationInvocationHandler.class);
+
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  static OperationInvocationHandler<?> getInstance(final EntityContainerInvocationHandler<?> containerHandler) {
+    return new OperationInvocationHandler(containerHandler);
+  }
+
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  static OperationInvocationHandler<?> getInstance(final EntityTypeInvocationHandler<?> entityHandler) {
+    return new OperationInvocationHandler(entityHandler);
+  }
+
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  static OperationInvocationHandler<?> getInstance(final EntityCollectionInvocationHandler<?, ?> collectionHandler) {
+    return new OperationInvocationHandler(collectionHandler);
+  }
+
+  @SuppressWarnings("unchecked")
+  private OperationInvocationHandler(final EntityContainerInvocationHandler<C> containerHandler) {
+
+    super(containerHandler.getClient(), containerHandler);
+
+    this.target = containerHandler;
+
+    this.targetFQN =
+            new FullQualifiedName(containerHandler.getSchemaName(), containerHandler.getEntityContainerName());
+
+    this.serviceRoot = containerHandler.getFactory().getServiceRoot();
+  }
+
+  @SuppressWarnings("unchecked")
+  private OperationInvocationHandler(final EntityTypeInvocationHandler<C> entityHandler) {
+    super(entityHandler.getClient(), entityHandler.containerHandler);
+
+    this.target = entityHandler;
+    this.targetFQN = entityHandler.getEntity().getTypeName();
+    this.serviceRoot = containerHandler.getFactory().getServiceRoot();
+  }
+
+  @SuppressWarnings("unchecked")
+  private OperationInvocationHandler(final EntityCollectionInvocationHandler<?, C> collectionHandler) {
+    super(collectionHandler.getClient(), collectionHandler.containerHandler);
+
+    this.target = collectionHandler;
+
+    final String typeName = ClassUtils.getEntityTypeName(collectionHandler.getEntityReference());
+    final String typeNamespace = ClassUtils.getNamespace(collectionHandler.getEntityReference());
+
+    this.targetFQN = new FullQualifiedName(typeNamespace, typeName);
+    this.serviceRoot = containerHandler.getFactory().getServiceRoot();
+  }
+
+  @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 {
+      final Annotation[] methodAnnots = method.getAnnotations();
+      if (methodAnnots[0] instanceof Operation) {
+        final Operation operation = (Operation) methodAnnots[0];
+
+        final Annotation[][] annotations = method.getParameterAnnotations();
+        final List<String> parameterNames;
+        final LinkedHashMap<Parameter, Object> parameters = new LinkedHashMap<Parameter, Object>();
+        if (annotations == null || annotations.length == 0) {
+          parameterNames = null;
+        } else {
+          parameterNames = new ArrayList<String>();
+          for (int i = 0; i < args.length; i++) {
+            for (Annotation paramAnnotation : annotations[i]) {
+              if (paramAnnotation instanceof Parameter) {
+                parameterNames.add(((Parameter) paramAnnotation).name());
+                parameters.put((Parameter) paramAnnotation, args[i]);
+              }
+            }
+
+            if (parameters.size() <= i) {
+              throw new IllegalArgumentException("Paramter " + i + " is not annotated as @Parameter");
+            }
+          }
+        }
+
+        final Map.Entry<URI, EdmOperation> edmOperation;
+        if (target instanceof EntityContainerInvocationHandler) {
+          edmOperation = getUnboundOperation(operation, parameterNames);
+        } else if (target instanceof EntityTypeInvocationHandler) {
+          edmOperation = getBoundOperation(operation, parameterNames);
+        } else if (target instanceof EntityCollectionInvocationHandler) {
+          edmOperation = getCollectionBoundOperation(operation, parameterNames);
+        } else {
+          throw new IllegalStateException("Invalid target invocation");
+        }
+
+        return invokeOperation(operation, method, parameters, edmOperation.getKey(), edmOperation.getValue());
+      } else {
+        throw new UnsupportedOperationException("Method not found: " + method);
+      }
+    }
+  }
+
+  private Map.Entry<URI, EdmOperation> getUnboundOperation(
+          final Operation operation, final List<String> parameterNames) {
+    final EdmEntityContainer container = client.getCachedEdm().getEntityContainer(targetFQN);
+    final EdmOperation edmOperation;
+
+    if (operation.type() == OperationType.FUNCTION) {
+      edmOperation = container.getFunctionImport(operation.name()).getUnboundFunction(parameterNames);
+    } else {
+      edmOperation = container.getActionImport(operation.name()).getUnboundAction();
+    }
+
+    final CommonURIBuilder<?> uriBuilder = getClient().getURIBuilder(this.serviceRoot).
+            appendOperationCallSegment(URIUtils.operationImportURISegment(container, edmOperation.getName()));
+
+    return new AbstractMap.SimpleEntry<URI, EdmOperation>(uriBuilder.build(), edmOperation);
+  }
+
+  private Map.Entry<URI, EdmOperation> getBoundOperation(
+          final Operation operation, final List<String> parameterNames) {
+    final CommonODataEntity entity = ((EntityTypeInvocationHandler<?>) target).getEntity();
+
+    final ODataOperation boundOp =
+            entity.getOperation(new FullQualifiedName(targetFQN.getNamespace(), operation.name()).toString());
+
+    final EdmOperation edmOperation;
+
+    if (operation.type() == OperationType.FUNCTION) {
+      edmOperation = client.getCachedEdm().getBoundFunction(
+              new FullQualifiedName(boundOp.getTitle()), entity.getTypeName(), false, parameterNames);
+    } else {
+      edmOperation = client.getCachedEdm().getBoundAction(
+              new FullQualifiedName(boundOp.getTitle()), entity.getTypeName(), false);
+    }
+
+    return new AbstractMap.SimpleEntry<URI, EdmOperation>(boundOp.getTarget(), edmOperation);
+  }
+
+  @SuppressWarnings("unchecked")
+  private Map.Entry<URI, EdmOperation> getCollectionBoundOperation(
+          final Operation operation, final List<String> parameterNames) {
+
+    final Edm edm = client.getCachedEdm();
+
+    final EdmOperation edmOperation;
+
+    if (operation.type() == OperationType.FUNCTION) {
+      edmOperation = client.getCachedEdm().getBoundFunction(
+              new FullQualifiedName(targetFQN.getNamespace(), operation.name()), targetFQN, true, parameterNames);
+    } else {
+      edmOperation = client.getCachedEdm().getBoundAction(
+              new FullQualifiedName(targetFQN.getNamespace(), operation.name()), targetFQN, true);
+    }
+
+    return new AbstractMap.SimpleEntry<URI, EdmOperation>(
+            ((EntityCollectionInvocationHandler<?, C>) target).getURI(), edmOperation);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/QueryImpl.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/QueryImpl.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/QueryImpl.java
new file mode 100644
index 0000000..00c3f73
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/commons/QueryImpl.java
@@ -0,0 +1,173 @@
+/*
+ * 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 org.apache.commons.lang3.StringUtils;
+import org.apache.olingo.client.api.CommonODataClient;
+import org.apache.olingo.client.api.uri.CommonURIBuilder;
+import org.apache.olingo.client.api.uri.URIFilter;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.ext.proxy.api.AbstractEntityCollection;
+import org.apache.olingo.ext.proxy.api.NoResultException;
+import org.apache.olingo.ext.proxy.api.NonUniqueResultException;
+import org.apache.olingo.ext.proxy.api.Query;
+import org.apache.olingo.ext.proxy.api.Sort;
+import org.apache.olingo.ext.proxy.utils.ClassUtils;
+
+public class QueryImpl<T extends Serializable, EC extends AbstractEntityCollection<T>> implements Query<T, EC> {
+
+  private static final long serialVersionUID = -300830736753191114L;
+
+  private final CommonODataClient client;
+
+  private final Class<T> typeRef;
+
+  private final Class<EC> collTypeRef;
+
+  private final EntitySetInvocationHandler handler;
+
+  private final URI baseURI;
+
+  private String filter;
+
+  private String orderBy;
+
+  private Integer maxResults;
+
+  private Integer firstResult;
+
+  @SuppressWarnings("unchecked")
+  QueryImpl(final CommonODataClient client,
+          final Class<EC> collTypeRef, final URI baseURI, final EntitySetInvocationHandler handler) {
+
+    this.client = client;
+    this.typeRef = (Class<T>) ClassUtils.extractTypeArg(collTypeRef);
+    this.collTypeRef = collTypeRef;
+    this.baseURI = baseURI;
+    this.handler = handler;
+  }
+
+  @Override
+  public Query<T, EC> setFilter(final String filter) {
+    this.filter = filter;
+    return this;
+  }
+
+  @Override
+  public Query<T, EC> setFilter(final URIFilter filter) {
+    this.filter = filter.build();
+    return this;
+  }
+
+  @Override
+  public String getFilter() {
+    return filter;
+  }
+
+  @Override
+  public Query<T, EC> setOrderBy(final Sort... sort) {
+    final StringBuilder builder = new StringBuilder();
+    for (Sort sortClause : sort) {
+      builder.append(sortClause.getKey()).append(' ').append(sortClause.getValue()).append(',');
+    }
+    builder.deleteCharAt(builder.length() - 1);
+
+    this.orderBy = builder.toString();
+    return this;
+  }
+
+  @Override
+  public Query<T, EC> setOrderBy(final String orderBy) {
+    this.orderBy = orderBy;
+    return this;
+  }
+
+  @Override
+  public String getOrderBy() {
+    return orderBy;
+  }
+
+  @Override
+  public Query<T, EC> setMaxResults(final int maxResults) throws IllegalArgumentException {
+    if (maxResults <= 0) {
+      throw new IllegalArgumentException("maxResults must be positive");
+    }
+
+    this.maxResults = maxResults;
+    return this;
+  }
+
+  @Override
+  public int getMaxResults() {
+    return maxResults;
+  }
+
+  @Override
+  public Query<T, EC> setFirstResult(final int firstResult) throws IllegalArgumentException {
+    if (firstResult <= 0) {
+      throw new IllegalArgumentException("firstResult must be positive");
+    }
+
+    this.firstResult = firstResult;
+    return this;
+  }
+
+  @Override
+  public int getFirstResult() {
+    return firstResult;
+  }
+
+  @Override
+  public T getSingleResult() throws NoResultException, NonUniqueResultException {
+    final EC result = getResult();
+    if (result.isEmpty()) {
+      throw new NoResultException();
+    }
+    if (result.size() > 1) {
+      throw new NonUniqueResultException();
+    }
+
+    return result.iterator().next();
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public EC getResult() {
+    final CommonURIBuilder<?> uriBuilder = client.getURIBuilder(this.baseURI.toASCIIString()).
+            appendNavigationSegment(new FullQualifiedName(
+            ClassUtils.getNamespace(typeRef), ClassUtils.getEntityTypeName(typeRef)).toString());
+
+    if (StringUtils.isNotBlank(filter)) {
+      uriBuilder.filter(filter);
+    }
+    if (StringUtils.isNotBlank(orderBy)) {
+      uriBuilder.orderBy(orderBy);
+    }
+    if (maxResults != null) {
+      uriBuilder.top(maxResults);
+    }
+    if (firstResult != null) {
+      uriBuilder.skip(firstResult);
+    }
+
+    return (EC) handler.fetchWholeEntitySet(uriBuilder.build(), typeRef, collTypeRef);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/AttachedEntity.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/AttachedEntity.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/AttachedEntity.java
new file mode 100644
index 0000000..a30be64
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/AttachedEntity.java
@@ -0,0 +1,41 @@
+/*
+ * 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.context;
+
+import org.apache.olingo.ext.proxy.commons.EntityTypeInvocationHandler;
+
+public class AttachedEntity {
+
+  private final EntityTypeInvocationHandler<?> entity;
+
+  private final AttachedEntityStatus status;
+
+  public AttachedEntity(final EntityTypeInvocationHandler<?> entity, final AttachedEntityStatus status) {
+    this.entity = entity;
+    this.status = status;
+  }
+
+  public EntityTypeInvocationHandler<?> getEntity() {
+    return entity;
+  }
+
+  public AttachedEntityStatus getStatus() {
+    return status;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/AttachedEntityStatus.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/AttachedEntityStatus.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/AttachedEntityStatus.java
new file mode 100644
index 0000000..6d0d986
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/AttachedEntityStatus.java
@@ -0,0 +1,44 @@
+/*
+ * 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.context;
+
+public enum AttachedEntityStatus {
+
+  /**
+   * Explicitely attached.
+   */
+  ATTACHED,
+  /**
+   * New object.
+   */
+  NEW,
+  /**
+   * Modified object.
+   */
+  CHANGED,
+  /**
+   * Deleted object.
+   */
+  DELETED,
+  /**
+   * Attached because explicitely liked to another object.
+   */
+  LINKED
+
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/Context.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/Context.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/Context.java
new file mode 100644
index 0000000..c77ce08
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/Context.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.ext.proxy.context;
+
+public class Context {
+
+  private final EntityContext entities;
+
+  public Context() {
+    this.entities = new EntityContext();
+
+  }
+
+  public EntityContext entityContext() {
+    return entities;
+  }
+
+  public void detachAll() {
+    entities.detachAll();
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/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
new file mode 100644
index 0000000..753f33e
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityContext.java
@@ -0,0 +1,199 @@
+/*
+ * 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.context;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.olingo.ext.proxy.commons.EntityTypeInvocationHandler;
+
+/**
+ * Entity context.
+ */
+public class EntityContext implements Iterable<AttachedEntity> {
+
+  /**
+   * Attached entities with not null key.
+   * <p>
+   * This map have to be used to search for entities by key.
+   */
+  private final Map<EntityUUID, EntityTypeInvocationHandler<?>> searchableEntities =
+          new HashMap<EntityUUID, EntityTypeInvocationHandler<?>>();
+
+  /**
+   * All attached entities (new entities included).
+   * <p>
+   * Attachment order will be maintained.
+   */
+  private final Map<EntityTypeInvocationHandler<?>, AttachedEntityStatus> allAttachedEntities =
+          new LinkedHashMap<EntityTypeInvocationHandler<?>, AttachedEntityStatus>();
+
+  /**
+   * Attaches an entity with status <tt>NEW</tt>.
+   * <p>
+   * Use this method to attach a new created entity.
+   *
+   * @see AttachedEntityStatus
+   * @param entity entity to be attached.
+   */
+  public void attachNew(final EntityTypeInvocationHandler<?> entity) {
+    if (allAttachedEntities.containsKey(entity)) {
+      throw new IllegalStateException("An entity with the same key has already been attached");
+    }
+    allAttachedEntities.put(entity, AttachedEntityStatus.NEW);
+  }
+
+  /**
+   * Attaches an existing entity with status <tt>ATTACHED</tt>.
+   * <p>
+   * Use this method to attach an existing entity.
+   *
+   * @see AttachedEntityStatus
+   * @param entity entity to be attached.
+   */
+  public void attach(final EntityTypeInvocationHandler<?> entity) {
+    attach(entity, AttachedEntityStatus.ATTACHED);
+  }
+
+  /**
+   * Attaches an entity with specified status.
+   * <p>
+   * Use this method to attach an existing entity.
+   *
+   * @see AttachedEntityStatus
+   * @param entity entity to be attached.
+   * @param status status.
+   */
+  public void attach(final EntityTypeInvocationHandler<?> entity, final AttachedEntityStatus status) {
+    if (isAttached(entity)) {
+      throw new IllegalStateException("An entity with the same profile has already been attached");
+    }
+
+    allAttachedEntities.put(entity, status);
+
+    if (entity.getUUID().getKey() != null) {
+      searchableEntities.put(entity.getUUID(), entity);
+    }
+  }
+
+  /**
+   * Detaches entity.
+   *
+   * @param entity entity to be detached.
+   */
+  public void detach(final EntityTypeInvocationHandler<?> entity) {
+    if (searchableEntities.containsKey(entity.getUUID())) {
+      searchableEntities.remove(entity.getUUID());
+    }
+    allAttachedEntities.remove(entity);
+  }
+
+  /**
+   * Detaches all attached entities.
+   * <p>
+   * Use this method to clears the entity context.
+   */
+  public void detachAll() {
+    allAttachedEntities.clear();
+    searchableEntities.clear();
+  }
+
+  /**
+   * Searches an entity with the specified key.
+   *
+   * @param uuid entity key.
+   * @return retrieved entity.
+   */
+  public EntityTypeInvocationHandler<?> getEntity(final EntityUUID uuid) {
+    return searchableEntities.get(uuid);
+  }
+
+  /**
+   * Gets entity status.
+   *
+   * @param entity entity to be retrieved.
+   * @return attached entity status.
+   */
+  public AttachedEntityStatus getStatus(final EntityTypeInvocationHandler<?> entity) {
+    if (!isAttached(entity)) {
+      throw new IllegalStateException("Entity is not in the context");
+    }
+
+    return allAttachedEntities.get(entity);
+  }
+
+  /**
+   * Changes attached entity status.
+   *
+   * @param entity attached entity to be modified.
+   * @param status new status.
+   */
+  public void setStatus(final EntityTypeInvocationHandler<?> entity, final AttachedEntityStatus status) {
+    if (!isAttached(entity)) {
+      throw new IllegalStateException("Entity is not in the context");
+    }
+
+    final AttachedEntityStatus current = allAttachedEntities.get(entity);
+
+    // Previously deleted object cannot be modified anymore.
+    if (current == AttachedEntityStatus.DELETED) {
+      throw new IllegalStateException("Entity has been previously deleted");
+    }
+
+    if (status == AttachedEntityStatus.NEW || status == AttachedEntityStatus.ATTACHED) {
+      throw new IllegalStateException("Entity status has already been initialized");
+    }
+
+    if ((status == AttachedEntityStatus.LINKED && current == AttachedEntityStatus.ATTACHED)
+            || (status == AttachedEntityStatus.CHANGED && current == AttachedEntityStatus.ATTACHED)
+            || (status == AttachedEntityStatus.CHANGED && current == AttachedEntityStatus.LINKED)
+            || (status == AttachedEntityStatus.DELETED)) {
+      allAttachedEntities.put(entity, status);
+    }
+  }
+
+  /**
+   * Checks if an entity is already attached.
+   *
+   * @param entity entity.
+   * @return <tt>true</tt> if is attached; <tt>false</tt> otherwise.
+   */
+  public boolean isAttached(final EntityTypeInvocationHandler<?> entity) {
+    return allAttachedEntities.containsKey(entity)
+            || (entity.getUUID().getKey() != null && searchableEntities.containsKey(entity.getUUID()));
+  }
+
+  /**
+   * Iterator.
+   *
+   * @return attached entities iterator.
+   */
+  @Override
+  public Iterator<AttachedEntity> iterator() {
+    final List<AttachedEntity> res = new ArrayList<AttachedEntity>();
+    for (Map.Entry<EntityTypeInvocationHandler<?>, AttachedEntityStatus> attachedEntity : allAttachedEntities.
+            entrySet()) {
+      res.add(new AttachedEntity(attachedEntity.getKey(), attachedEntity.getValue()));
+    }
+    return res.iterator();
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityLinkDesc.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityLinkDesc.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityLinkDesc.java
new file mode 100644
index 0000000..e83b2c3
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityLinkDesc.java
@@ -0,0 +1,104 @@
+/*
+ * 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.context;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+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.commons.api.domain.ODataLinkType;
+import org.apache.olingo.ext.proxy.commons.EntityTypeInvocationHandler;
+
+public class EntityLinkDesc implements Serializable {
+
+  private static final long serialVersionUID = 704670372070370762L;
+
+  private final String sourceName;
+
+  private final EntityTypeInvocationHandler<?> source;
+
+  private final Collection<EntityTypeInvocationHandler<?>> targets;
+
+  private final ODataLinkType type;
+
+  public EntityLinkDesc(
+          final String sourceName,
+          final EntityTypeInvocationHandler<?> source,
+          final Collection<EntityTypeInvocationHandler<?>> target,
+          final ODataLinkType type) {
+    this.sourceName = sourceName;
+    this.source = source;
+    this.targets = target;
+    this.type = type;
+  }
+
+  public EntityLinkDesc(
+          final String sourceName,
+          final EntityTypeInvocationHandler<?> source,
+          final EntityTypeInvocationHandler<?> target,
+          final ODataLinkType type) {
+    this.sourceName = sourceName;
+    this.source = source;
+    this.targets = Collections.<EntityTypeInvocationHandler<?>>singleton(target);
+    this.type = type;
+  }
+
+  public String getSourceName() {
+    return sourceName;
+  }
+
+  public EntityTypeInvocationHandler<?> getSource() {
+    return source;
+  }
+
+  public Collection<EntityTypeInvocationHandler<?>> getTargets() {
+    return targets;
+  }
+
+  public ODataLinkType getType() {
+    return type;
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public boolean equals(Object obj) {
+    return EqualsBuilder.reflectionEquals(this, obj);
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public int hashCode() {
+    return HashCodeBuilder.reflectionHashCode(this);
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @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/context/EntityUUID.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityUUID.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityUUID.java
new file mode 100644
index 0000000..de7a6f1
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/context/EntityUUID.java
@@ -0,0 +1,102 @@
+/*
+ * 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.context;
+
+import java.io.Serializable;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+
+public class EntityUUID implements Serializable {
+
+  private static final long serialVersionUID = 4855025769803086495L;
+
+  // needed by equals and hashcode
+  private final int tempKey;
+
+  private final String containerName;
+
+  private final String entitySetName;
+
+  private final FullQualifiedName name;
+
+  private final Object key;
+
+  public EntityUUID(
+          final String containerName,
+          final String entitySetName,
+          final FullQualifiedName name) {
+    this(containerName, entitySetName, name, null);
+  }
+
+  public EntityUUID(
+          final String containerName,
+          final String entitySetName,
+          final FullQualifiedName name,
+          final Object key) {
+    this.containerName = containerName;
+    this.entitySetName = entitySetName;
+    this.name = name;
+    this.key = key;
+    this.tempKey = (int) (Math.random() * 1000000);
+  }
+
+  public String getContainerName() {
+    return containerName;
+  }
+
+  public String getEntitySetName() {
+    return entitySetName;
+  }
+
+  public FullQualifiedName getName() {
+    return name;
+  }
+
+  public Object getKey() {
+    return key;
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public boolean equals(Object obj) {
+    return key == null
+            ? EqualsBuilder.reflectionEquals(this, obj)
+            : EqualsBuilder.reflectionEquals(this, obj, "tempKey");
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public int hashCode() {
+    return HashCodeBuilder.reflectionHashCode(this, "tempKey");
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public String toString() {
+    return name.getNamespace() + ":" + containerName + ":" + entitySetName + ":" + name.getName()
+            + "(" + (key == null ? null : key.toString()) + ")";
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/ClassUtils.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/ClassUtils.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/ClassUtils.java
new file mode 100644
index 0000000..c4f325f
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/ClassUtils.java
@@ -0,0 +1,142 @@
+/*
+ * 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.annotation.Annotation;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.olingo.ext.proxy.api.annotations.CompoundKey;
+import org.apache.olingo.ext.proxy.api.annotations.EntityType;
+import org.apache.olingo.ext.proxy.api.annotations.Key;
+import org.apache.olingo.ext.proxy.api.annotations.KeyRef;
+import org.apache.olingo.ext.proxy.api.annotations.Namespace;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class ClassUtils {
+
+  /**
+   * Logger.
+   */
+  private static final Logger LOG = LoggerFactory.getLogger(ClassUtils.class);
+
+  private ClassUtils() {
+    // Empty private constructor for static utility classes
+  }
+
+  @SuppressWarnings("unchecked")
+  public static Class<?> extractTypeArg(final Class<?> paramType) {
+    final Type[] params = ((ParameterizedType) paramType.getGenericInterfaces()[0]).getActualTypeArguments();
+    return (Class<?>) params[0];
+  }
+
+  public static Method findGetterByAnnotatedName(
+          final Class<?> clazz, final Class<? extends Annotation> ann, final String name) {
+    final Method[] methods = clazz.getMethods();
+
+    Method result = null;
+    for (int i = 0; i < methods.length && result == null; i++) {
+      final Annotation annotation = methods[i].getAnnotation(ann);
+      try {
+        if ((annotation != null)
+                && methods[i].getName().startsWith("get") // Assumption: getter is always prefixed by 'get' word 
+                && name.equals(ann.getMethod("name").invoke(annotation))) {
+          result = methods[i];
+        }
+      } catch (Exception e) {
+        LOG.warn("Error retrieving value annotation name for {}.{}", clazz.getName(), methods[i].getName());
+      }
+    }
+
+    return result;
+  }
+
+  @SuppressWarnings("unchecked")
+  public static <ANN extends Annotation> ANN getAnnotation(final Class<ANN> reference, final AccessibleObject obj) {
+    final Annotation ann = obj.getAnnotation(reference);
+    return ann == null ? null : (ANN) ann;
+  }
+
+  public static Class<?> getCompoundKeyRef(final Class<?> entityTypeRef) {
+    if (entityTypeRef.getAnnotation(EntityType.class) == null) {
+      throw new IllegalArgumentException("Invalid annotation for entity type " + entityTypeRef);
+    }
+
+    final Annotation ann = entityTypeRef.getAnnotation(KeyRef.class);
+
+    return ann == null || ((KeyRef) ann).value().getAnnotation(CompoundKey.class) == null
+            ? null
+            : ((KeyRef) ann).value();
+  }
+
+  public static Class<?> getKeyRef(final Class<?> entityTypeRef) {
+    Class<?> res = getCompoundKeyRef(entityTypeRef);
+
+    if (res == null) {
+      final Set<Method> keyGetters = new HashSet<Method>();
+
+      for (Method method : entityTypeRef.getDeclaredMethods()) {
+        if (method.getName().startsWith("get") && method.getAnnotation(Key.class) != null) {
+          keyGetters.add(method);
+        }
+      }
+
+      if (keyGetters.size() == 1) {
+        res = keyGetters.iterator().next().getReturnType();
+      } else {
+        throw new IllegalStateException(entityTypeRef.getSimpleName() + "'s key reference not found");
+      }
+    }
+
+    return res;
+  }
+
+  public static String getEntityTypeName(final Class<?> ref) {
+    final Annotation annotation = ref.getAnnotation(EntityType.class);
+    if (!(annotation instanceof EntityType)) {
+      throw new IllegalArgumentException(ref.getPackage().getName()
+              + " is not annotated as @" + EntityType.class.getSimpleName());
+    }
+    return ((EntityType) annotation).name();
+  }
+
+  public static String getNamespace(final Class<?> ref) {
+    final Annotation annotation = ref.getAnnotation(Namespace.class);
+    if (!(annotation instanceof Namespace)) {
+      throw new IllegalArgumentException(ref.getName()
+              + " is not annotated as @" + Namespace.class.getSimpleName());
+    }
+    return ((Namespace) annotation).value();
+  }
+
+  public static Void returnVoid()
+          throws NoSuchMethodException, InstantiationException, IllegalAccessException,
+          IllegalArgumentException, InvocationTargetException {
+
+    final Constructor<Void> voidConstructor = Void.class.getDeclaredConstructor();
+    voidConstructor.setAccessible(true);
+    return voidConstructor.newInstance();
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/EngineUtils.java
----------------------------------------------------------------------
diff --git a/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/EngineUtils.java b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/EngineUtils.java
new file mode 100644
index 0000000..d625c80
--- /dev/null
+++ b/ext/client-proxy/src/main/java/org/apache/olingo/ext/proxy/utils/EngineUtils.java
@@ -0,0 +1,423 @@
+/*
+ * 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.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import org.apache.olingo.client.api.CommonEdmEnabledODataClient;
+import org.apache.olingo.client.api.v3.UnsupportedInV3Exception;
+import org.apache.olingo.client.core.edm.xml.AbstractComplexType;
+import org.apache.olingo.commons.api.domain.CommonODataEntity;
+import org.apache.olingo.commons.api.domain.CommonODataProperty;
+import org.apache.olingo.commons.api.domain.ODataLink;
+import org.apache.olingo.commons.api.domain.ODataValue;
+import org.apache.olingo.commons.api.edm.Edm;
+import org.apache.olingo.commons.api.edm.EdmType;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
+import org.apache.olingo.commons.core.edm.EdmTypeInfo;
+import org.apache.olingo.ext.proxy.api.annotations.ComplexType;
+import org.apache.olingo.ext.proxy.api.annotations.CompoundKeyElement;
+import org.apache.olingo.ext.proxy.api.annotations.Key;
+import org.apache.olingo.ext.proxy.api.annotations.Property;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class EngineUtils {
+
+  /**
+   * Logger.
+   */
+  private static final Logger LOG = LoggerFactory.getLogger(EngineUtils.class);
+
+  private EngineUtils() {
+    // Empty private constructor for static utility classes
+  }
+
+  public static ODataLink getNavigationLink(final String name, final CommonODataEntity entity) {
+    ODataLink res = null;
+    final List<ODataLink> links = entity.getNavigationLinks();
+
+    for (int i = 0; i < links.size() && res == null; i++) {
+      if (links.get(i).getName().equalsIgnoreCase(name)) {
+        res = links.get(i);
+      }
+    }
+    return res;
+  }
+
+  public static ODataValue getODataValue(
+          final CommonEdmEnabledODataClient<?> client, final EdmTypeInfo type, final Object obj) {
+
+    final ODataValue value;
+
+    if (type.isCollection()) {
+      value = client.getObjectFactory().newCollectionValue(type.getFullQualifiedName().toString());
+
+      final EdmTypeInfo intType = new EdmTypeInfo.Builder().
+              setEdm(client.getCachedEdm()).setTypeExpression(type.getFullQualifiedName().toString()).build();
+
+      for (Object collectionItem : (Collection) obj) {
+        if (intType.isPrimitiveType()) {
+          value.asCollection().add(getODataValue(client, intType, collectionItem).asPrimitive());
+        } else if (intType.isComplexType()) {
+          value.asCollection().add(getODataValue(client, intType, collectionItem).asComplex());
+        } else if (intType.isEnumType()) {
+          if (client.getServiceVersion().compareTo(ODataServiceVersion.V30) <= 0) {
+            throw new UnsupportedInV3Exception();
+          } else {
+            value.asCollection().add(((org.apache.olingo.commons.api.domain.v4.ODataValue) getODataValue(
+                    client, intType, collectionItem)).asEnum());
+          }
+
+        } else {
+          throw new UnsupportedOperationException("Usupported object type " + intType.getFullQualifiedName());
+        }
+      }
+    } else if (type.isComplexType()) {
+      value = client.getObjectFactory().newComplexValue(type.getFullQualifiedName().toString());
+
+      if (obj.getClass().isAnnotationPresent(ComplexType.class)) {
+        for (Method method : obj.getClass().getMethods()) {
+          final Property complexPropertyAnn = method.getAnnotation(Property.class);
+          try {
+            if (complexPropertyAnn != null) {
+              value.asComplex().add(getODataComplexProperty(
+                      client, type.getFullQualifiedName(), complexPropertyAnn.name(), method.invoke(obj)));
+            }
+          } catch (Exception ignore) {
+            // ignore value
+            LOG.warn("Error attaching complex field '{}'", complexPropertyAnn.name(), ignore);
+          }
+        }
+      } else {
+        throw new IllegalArgumentException(
+                "Object '" + obj.getClass().getSimpleName() + "' is not a complex value");
+      }
+    } else if (type.isEnumType()) {
+      if (client.getServiceVersion().compareTo(ODataServiceVersion.V30) <= 0) {
+        throw new UnsupportedInV3Exception();
+      } else {
+        value = ((org.apache.olingo.commons.api.domain.v4.ODataObjectFactory) client.getObjectFactory()).
+                newEnumValue(type.getFullQualifiedName().toString(), ((Enum) obj).name());
+      }
+    } else {
+      value = client.getObjectFactory().newPrimitiveValueBuilder().setType(type.getPrimitiveTypeKind()).setValue(obj).
+              build();
+    }
+
+    return value;
+  }
+
+  private static CommonODataProperty getODataEntityProperty(
+          final CommonEdmEnabledODataClient<?> client,
+          final FullQualifiedName entity,
+          final String property,
+          final Object obj) {
+    final EdmType edmType = client.getCachedEdm().getEntityType(entity).getProperty(property).getType();
+    final EdmTypeInfo type = new EdmTypeInfo.Builder().
+            setEdm(client.getCachedEdm()).setTypeExpression(edmType.getFullQualifiedName().toString()).build();
+
+    return getODataProperty(client, property, type, obj);
+  }
+
+  private static CommonODataProperty getODataComplexProperty(
+          final CommonEdmEnabledODataClient<?> client,
+          final FullQualifiedName complex,
+          final String property,
+          final Object obj) {
+    final EdmType edmType = client.getCachedEdm().getComplexType(complex).getProperty(property).getType();
+    final EdmTypeInfo type = new EdmTypeInfo.Builder().
+            setEdm(client.getCachedEdm()).setTypeExpression(edmType.getFullQualifiedName().toString()).build();
+
+    return getODataProperty(client, property, type, obj);
+  }
+
+  private static CommonODataProperty getODataProperty(
+          final CommonEdmEnabledODataClient<?> client, final String name, final EdmTypeInfo type, final Object obj) {
+    final CommonODataProperty oprop;
+
+    try {
+      if (type == null || obj == null) {
+        oprop = client.getObjectFactory().newPrimitiveProperty(name, null);
+      } else if (type.isCollection()) {
+        // create collection property
+        oprop = client.getObjectFactory().newCollectionProperty(name, getODataValue(client, type, obj).asCollection());
+      } else if (type.isPrimitiveType()) {
+        // create a primitive property
+        oprop = client.getObjectFactory().newPrimitiveProperty(name, getODataValue(client, type, obj).asPrimitive());
+      } else if (type.isComplexType()) {
+        // create a complex property
+        oprop = client.getObjectFactory().newComplexProperty(name, getODataValue(client, type, obj).asComplex());
+      } else if (type.isEnumType()) {
+        if (client.getServiceVersion().compareTo(ODataServiceVersion.V30) <= 0) {
+          throw new UnsupportedInV3Exception();
+        } else {
+          oprop = ((org.apache.olingo.commons.api.domain.v4.ODataObjectFactory) client.getObjectFactory()).
+                  newEnumProperty(name,
+                  ((org.apache.olingo.commons.api.domain.v4.ODataValue) getODataValue(client, type, obj)).asEnum());
+        }
+      } else {
+        throw new UnsupportedOperationException("Usupported object type " + type.getFullQualifiedName());
+      }
+
+      return oprop;
+    } catch (Exception e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  public static void addProperties(
+          final CommonEdmEnabledODataClient<?> client,
+          final Map<String, Object> changes,
+          final CommonODataEntity entity) {
+
+    for (Map.Entry<String, Object> property : changes.entrySet()) {
+      // if the getter exists and it is annotated as expected then get value/value and add a new property
+      final CommonODataProperty odataProperty = entity.getProperty(property.getKey());
+      if (odataProperty != null) {
+        entity.getProperties().remove(odataProperty);
+      }
+
+      ((List<CommonODataProperty>) entity.getProperties()).add(
+              getODataEntityProperty(client, entity.getTypeName(), property.getKey(), property.getValue()));
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private static void setPropertyValue(final Object bean, final Method getter, final Object value)
+          throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+
+    // Assumption: setter is always prefixed by 'set' word
+    final String setterName = getter.getName().replaceFirst("get", "set");
+    bean.getClass().getMethod(setterName, getter.getReturnType()).invoke(bean, value);
+  }
+
+  public static Object getKey(
+          final Edm metadata, final Class<?> entityTypeRef, final CommonODataEntity entity) {
+    final Object res;
+
+    if (entity.getProperties().isEmpty()) {
+      res = null;
+    } else {
+      final Class<?> keyRef = ClassUtils.getCompoundKeyRef(entityTypeRef);
+      if (keyRef == null) {
+        final CommonODataProperty property = entity.getProperty(firstValidEntityKey(entityTypeRef));
+        res = property == null || !property.hasPrimitiveValue()
+                ? null
+                : property.getPrimitiveValue().toValue();
+
+      } else {
+        try {
+          res = keyRef.newInstance();
+          populate(metadata, res, CompoundKeyElement.class, entity.getProperties().iterator());
+        } catch (Exception e) {
+          LOG.error("Error population compound key {}", keyRef.getSimpleName(), e);
+          throw new IllegalArgumentException("Cannot populate compound key");
+        }
+      }
+    }
+
+    return res;
+  }
+
+  @SuppressWarnings({"unchecked"})
+  public static void populate(
+          final Edm metadata,
+          final Object bean,
+          final Class<? extends Annotation> getterAnn,
+          final Iterator<? extends CommonODataProperty> propItor) {
+
+    if (bean != null) {
+      while (propItor.hasNext()) {
+        final CommonODataProperty property = propItor.next();
+
+        final Method getter =
+                ClassUtils.findGetterByAnnotatedName(bean.getClass(), getterAnn, property.getName());
+
+        if (getter == null) {
+          LOG.warn("Could not find any property annotated as {} in {}",
+                  property.getName(), bean.getClass().getName());
+        } else {
+          try {
+            if (property.hasNullValue()) {
+              setPropertyValue(bean, getter, null);
+            }
+            if (property.hasPrimitiveValue()) {
+              setPropertyValue(bean, getter, property.getPrimitiveValue().toValue());
+            }
+            if (property.hasComplexValue()) {
+              final Object complex = getter.getReturnType().newInstance();
+              populate(metadata, complex, Property.class, property.getValue().asComplex().iterator());
+              setPropertyValue(bean, getter, complex);
+            }
+            if (property.hasCollectionValue()) {
+              final ParameterizedType collType = (ParameterizedType) getter.getGenericReturnType();
+              final Class<?> collItemClass = (Class<?>) collType.getActualTypeArguments()[0];
+
+              Collection<Object> collection = (Collection<Object>) getter.invoke(bean);
+              if (collection == null) {
+                collection = new ArrayList<Object>();
+                setPropertyValue(bean, getter, collection);
+              }
+
+              final Iterator<ODataValue> collPropItor = property.getValue().asCollection().iterator();
+              while (collPropItor.hasNext()) {
+                final ODataValue value = collPropItor.next();
+                if (value.isPrimitive()) {
+                  collection.add(value.asPrimitive().toValue());
+                }
+                if (value.isComplex()) {
+                  final Object collItem = collItemClass.newInstance();
+                  populate(metadata, collItem, Property.class, value.asComplex().iterator());
+                  collection.add(collItem);
+                }
+              }
+            }
+          } catch (Exception e) {
+            LOG.error("Could not set property {} on {}", getter, bean, e);
+          }
+        }
+      }
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  public static Object getValueFromProperty(final Edm metadata, final CommonODataProperty property)
+          throws InstantiationException, IllegalAccessException {
+
+    final Object value;
+
+    if (property == null || property.hasNullValue()) {
+      value = null;
+    } else if (property.hasCollectionValue()) {
+      value = new ArrayList<Object>();
+
+      final Iterator<ODataValue> collPropItor = property.getValue().asCollection().iterator();
+      while (collPropItor.hasNext()) {
+        final ODataValue odataValue = collPropItor.next();
+        if (odataValue.isPrimitive()) {
+          ((Collection) value).add(odataValue.asPrimitive().toValue());
+        }
+        if (odataValue.isComplex()) {
+          final Object collItem =
+                  buildComplexInstance(metadata, property.getName(), odataValue.asComplex().iterator());
+          ((Collection) value).add(collItem);
+        }
+      }
+    } else if (property.hasPrimitiveValue()) {
+      value = property.getPrimitiveValue().toValue();
+    } else if (property.hasComplexValue()) {
+      value = buildComplexInstance(
+              metadata, property.getValue().asComplex().getTypeName(), property.getValue().asComplex().iterator());
+    } else {
+      throw new IllegalArgumentException("Invalid property " + property);
+    }
+
+    return value;
+  }
+
+  @SuppressWarnings("unchecked")
+  private static <C extends AbstractComplexType> C buildComplexInstance(
+          final Edm metadata, final String name, final Iterator<CommonODataProperty> properties) {
+
+    for (C complex : (Iterable<C>) ServiceLoader.load(AbstractComplexType.class)) {
+      final ComplexType ann = complex.getClass().getAnnotation(ComplexType.class);
+      final String fn = ann == null ? null : ClassUtils.getNamespace(complex.getClass()) + "." + ann.name();
+
+      if (name.equals(fn)) {
+        populate(metadata, complex, Property.class, properties);
+        return complex;
+      }
+    }
+
+    return null;
+  }
+
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  public static Object getValueFromProperty(final Edm metadata, final CommonODataProperty property, final Type type)
+          throws InstantiationException, IllegalAccessException {
+
+    final Object value;
+
+    if (property == null || property.hasNullValue()) {
+      value = null;
+    } else if (property.hasCollectionValue()) {
+      value = new ArrayList();
+
+      final ParameterizedType collType = (ParameterizedType) type;
+      final Class<?> collItemClass = (Class<?>) collType.getActualTypeArguments()[0];
+
+      final Iterator<ODataValue> collPropItor = property.getValue().asCollection().iterator();
+      while (collPropItor.hasNext()) {
+        final ODataValue odataValue = collPropItor.next();
+        if (odataValue.isPrimitive()) {
+          ((Collection) value).add(odataValue.asPrimitive().toValue());
+        }
+        if (odataValue.isComplex()) {
+          final Object collItem = collItemClass.newInstance();
+          populate(metadata, collItem, Property.class, odataValue.asComplex().iterator());
+          ((Collection) value).add(collItem);
+        }
+      }
+    } else if (property.hasPrimitiveValue()) {
+      value = property.getPrimitiveValue().toValue();
+    } else if (property.hasComplexValue()) {
+      value = ((Class<?>) type).newInstance();
+      populate(metadata, value, Property.class, property.getValue().asComplex().iterator());
+    } else {
+      throw new IllegalArgumentException("Invalid property " + property);
+    }
+
+    return value;
+  }
+
+  private static String firstValidEntityKey(final Class<?> entityTypeRef) {
+    for (Method method : entityTypeRef.getDeclaredMethods()) {
+      if (method.getAnnotation(Key.class) != null) {
+        final Annotation ann = method.getAnnotation(Property.class);
+        if (ann != null) {
+          return ((Property) ann).name();
+        }
+      }
+    }
+    return null;
+  }
+
+  public static URI getEditMediaLink(final String name, final CommonODataEntity entity) {
+    for (ODataLink editMediaLink : entity.getEditMediaLinks()) {
+      if (name.equalsIgnoreCase(editMediaLink.getName())) {
+        return editMediaLink.getLink();
+      }
+    }
+
+    throw new IllegalArgumentException("Invalid streamed property " + name);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/AbstractUtility.java
----------------------------------------------------------------------
diff --git a/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/AbstractUtility.java b/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/AbstractUtility.java
index e52d278..fab1893 100644
--- a/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/AbstractUtility.java
+++ b/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/AbstractUtility.java
@@ -389,6 +389,10 @@ public abstract class AbstractUtility {
     return StringUtils.uncapitalize(str);
   }
 
+  public String join(final Object[] array, String sep) {
+    return StringUtils.join(array, sep);
+  }
+
   public Map<String, String> getFcProperties(final EdmProperty property) {
     return Collections.<String, String>emptyMap();
   }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6b7be9de/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/V3MetadataMojo.java
----------------------------------------------------------------------
diff --git a/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/V3MetadataMojo.java b/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/V3MetadataMojo.java
index acd9f75..28bcdd0 100644
--- a/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/V3MetadataMojo.java
+++ b/ext/pojogen-maven-plugin/src/main/java/org/apache/olingo/ext/pojogen/V3MetadataMojo.java
@@ -156,6 +156,7 @@ public class V3MetadataMojo extends AbstractMetadataMojo {
                 for (EdmEntityContainer container : schema.getEntityContainers()) {
                     objs.clear();
                     objs.put("container", container);
+                    objs.put("namespace", schema.getNamespace());
                     parseObj(base, pkg, "container",
                             utility.capitalize(container.getName()) + ".java", objs);