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();
+ }
+}