You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by dh...@apache.org on 2014/09/01 09:19:03 UTC

[3/3] git commit: CAMEL-7773 Initial version of Camel-Olingo2 component

CAMEL-7773 Initial version of Camel-Olingo2 component


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/9a92064c
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/9a92064c
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/9a92064c

Branch: refs/heads/master
Commit: 9a92064cf9977a1cafa916441995e5e7804ec19e
Parents: 89a8b35
Author: Dhiraj Bokde <dh...@yahoo.com>
Authored: Mon Sep 1 00:18:26 2014 -0700
Committer: Dhiraj Bokde <dh...@yahoo.com>
Committed: Mon Sep 1 00:18:26 2014 -0700

----------------------------------------------------------------------
 .../camel-olingo2/camel-olingo2-api/pom.xml     | 141 +++
 .../camel/component/olingo2/api/Olingo2App.java | 134 +++
 .../olingo2/api/Olingo2ResponseHandler.java     |  43 +
 .../api/batch/Olingo2BatchChangeRequest.java    |  94 ++
 .../api/batch/Olingo2BatchQueryRequest.java     |  71 ++
 .../olingo2/api/batch/Olingo2BatchRequest.java  |  39 +
 .../olingo2/api/batch/Olingo2BatchResponse.java |  65 ++
 .../component/olingo2/api/batch/Operation.java  |  35 +
 .../api/impl/AbstractFutureCallback.java        | 104 +++
 .../component/olingo2/api/impl/HttpMerge.java   |  47 +
 .../olingo2/api/impl/Olingo2AppImpl.java        | 901 +++++++++++++++++++
 .../olingo2/api/Olingo2AppIntegrationTest.java  | 560 ++++++++++++
 .../src/test/resources/log4j.properties         |  39 +
 .../camel-olingo2-component/pom.xml             | 208 +++++
 .../component/olingo2/Olingo2Component.java     | 152 ++++
 .../component/olingo2/Olingo2Configuration.java | 158 ++++
 .../component/olingo2/Olingo2Consumer.java      |  93 ++
 .../component/olingo2/Olingo2Endpoint.java      | 281 ++++++
 .../component/olingo2/Olingo2Producer.java      | 106 +++
 .../olingo2/internal/Olingo2Constants.java      |  29 +
 .../internal/Olingo2PropertiesHelper.java       |  39 +
 .../services/org/apache/camel/component/olingo2 |   1 +
 .../src/signatures/olingo-api-signature.txt     |   7 +
 .../olingo2/AbstractOlingo2TestSupport.java     |  82 ++
 .../olingo2/Olingo2AppIntegrationTest.java      | 235 +++++
 .../src/test/resources/log4j.properties         |  39 +
 .../src/test/resources/test-options.properties  |  19 +
 components/camel-olingo2/pom.xml                |  65 ++
 components/pom.xml                              |   1 +
 29 files changed, 3788 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-api/pom.xml
----------------------------------------------------------------------
diff --git a/components/camel-olingo2/camel-olingo2-api/pom.xml b/components/camel-olingo2/camel-olingo2-api/pom.xml
new file mode 100644
index 0000000..971e402
--- /dev/null
+++ b/components/camel-olingo2/camel-olingo2-api/pom.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.camel.component.olingo2</groupId>
+    <artifactId>camel-olingo2-parent</artifactId>
+    <version>2.14-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>camel-olingo2-api</artifactId>
+  <name>Camel :: Olingo2 Component API</name>
+  <description>Camel API for Apache Olingo2</description>
+  <packaging>bundle</packaging>
+
+  <properties>
+    <camel.osgi.export.pkg>org.apache.camel.component.olingo2.api*</camel.osgi.export.pkg>
+    <olingo2-version>2.0.0</olingo2-version>
+    <http-asyncclient-version>4.0.2</http-asyncclient-version>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.olingo</groupId>
+      <artifactId>olingo-odata2-api</artifactId>
+      <version>${olingo2-version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.olingo</groupId>
+      <artifactId>olingo-odata2-core</artifactId>
+      <version>${olingo2-version}</version>
+      <exclusions>
+        <exclusion>
+          <groupId>javax.ws.rs</groupId>
+          <artifactId>javax.ws.rs-api</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpasyncclient</artifactId>
+      <version>${http-asyncclient-version}</version>
+    </dependency>
+
+    <!-- logging -->
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <version>1.7.7</version>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+      <version>1.7.7</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+      <version>1.2.17</version>
+      <scope>test</scope>
+    </dependency>
+
+    <!-- testing -->
+    <dependency>
+      <groupId>org.apache.camel</groupId>
+      <artifactId>camel-test</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <defaultGoal>install</defaultGoal>
+
+    <plugins>
+      
+      <!-- to generate API Javadoc -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>2.9.1</version>
+        <executions>
+          <execution>
+            <id>add-javadoc</id>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+            <configuration>
+              <attach>true</attach>
+              <source>1.6</source>
+              <quiet>true</quiet>
+              <detectOfflineLinks>false</detectOfflineLinks>
+              <javadocVersion>1.6</javadocVersion>
+              <encoding>UTF-8</encoding>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+    </plugins>
+  </build>
+
+  <!-- Disable Java 8 doclint checks to avoid Javadoc plugin failures -->
+  <profiles>
+    <profile>
+      <id>doclint-java8-disable</id>
+      <activation>
+        <jdk>[1.8,</jdk>
+      </activation>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-javadoc-plugin</artifactId>
+            <configuration>
+              <additionalparam>-Xdoclint:none</additionalparam>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+
+</project>

http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/Olingo2App.java
----------------------------------------------------------------------
diff --git a/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/Olingo2App.java b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/Olingo2App.java
new file mode 100644
index 0000000..751b798
--- /dev/null
+++ b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/Olingo2App.java
@@ -0,0 +1,134 @@
+/**
+ * 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.camel.component.olingo2.api;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.component.olingo2.api.batch.Olingo2BatchResponse;
+import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
+import org.apache.olingo.odata2.api.edm.Edm;
+
+/**
+ * Olingo2 Client Api Interface.
+ */
+public interface Olingo2App {
+
+    /**
+     * Sets Service base URI.
+     * @param serviceUri
+     */
+    void setServiceUri(String serviceUri);
+
+    /**
+     * Returns Service base URI.
+     * @return service base URI.
+     */
+    String getServiceUri();
+
+    /**
+     * Sets custom Http headers to add to every service request.
+     * @param httpHeaders custom Http headers.
+     */
+    void setHttpHeaders(Map<String, String> httpHeaders);
+
+    /**
+     * Returns custom Http headers.
+     * @return custom Http headers.
+     */
+    Map<String, String> getHttpHeaders();
+
+    /**
+     * Returns content type for service calls. Defaults to <code>application/json;charset=utf-8</code>.
+     * @return content type.
+     */
+    String getContentType();
+
+    /**
+     * Set default service call content type.
+     * @param contentType content type.
+     */
+    void setContentType(String contentType);
+
+    /**
+     * Closes resources.
+     */
+    void close();
+
+    /**
+     * Reads an OData resource and invokes callback with appropriate result.
+     * @param edm Service Edm, read from calling <code>read(null, "$metdata", null, responseHandler)</code>
+     * @param resourcePath OData Resource path
+     * @param queryParams OData query params
+     *                    from http://www.odata.org/documentation/odata-version-2-0/uri-conventions#SystemQueryOptions
+     * @param responseHandler callback handler
+     */
+    <T> void read(Edm edm, String resourcePath, Map<String, String> queryParams,
+                  Olingo2ResponseHandler<T> responseHandler);
+
+    /**
+     * Deletes an OData resource and invokes callback
+     * with {@link org.apache.olingo.odata2.api.commons.HttpStatusCodes} on success, or with exception on failure.
+     * @param resourcePath resource path for Entry
+     * @param responseHandler {@link org.apache.olingo.odata2.api.commons.HttpStatusCodes} callback handler
+     */
+    void delete(String resourcePath, Olingo2ResponseHandler<HttpStatusCodes> responseHandler);
+
+    /**
+     * Creates a new OData resource.
+     * @param edm service Edm
+     * @param resourcePath resource path to create
+     * @param data request data
+     * @param responseHandler callback handler
+     */
+    <T> void create(Edm edm, String resourcePath, Object data, Olingo2ResponseHandler<T> responseHandler);
+
+    /**
+     * Updates an OData resource.
+     * @param edm service Edm
+     * @param resourcePath resource path to update
+     * @param data updated data
+     * @param responseHandler {@link org.apache.olingo.odata2.api.ep.entry.ODataEntry} callback handler
+     */
+    <T> void update(Edm edm, String resourcePath, Object data, Olingo2ResponseHandler<T> responseHandler);
+
+    /**
+     * Patches/merges an OData resource using HTTP PATCH.
+     * @param edm service Edm
+     * @param resourcePath resource path to update
+     * @param data patch/merge data
+     * @param responseHandler {@link org.apache.olingo.odata2.api.ep.entry.ODataEntry} callback handler
+     */
+    <T> void patch(Edm edm, String resourcePath, Object data, Olingo2ResponseHandler<T> responseHandler);
+
+    /**
+     * Patches/merges an OData resource using HTTP MERGE.
+     * @param edm service Edm
+     * @param resourcePath resource path to update
+     * @param data patch/merge data
+     * @param responseHandler {@link org.apache.olingo.odata2.api.ep.entry.ODataEntry} callback handler
+     */
+    <T> void merge(Edm edm, String resourcePath, Object data, Olingo2ResponseHandler<T> responseHandler);
+
+    /**
+     * Executes a batch request.
+     * @param edm service Edm
+     * @param data ordered {@link org.apache.camel.component.olingo2.api.batch.Olingo2BatchRequest} list
+     * @param responseHandler callback handler
+     */
+    void batch(Edm edm, Object data, Olingo2ResponseHandler<List<Olingo2BatchResponse>> responseHandler);
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/Olingo2ResponseHandler.java
----------------------------------------------------------------------
diff --git a/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/Olingo2ResponseHandler.java b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/Olingo2ResponseHandler.java
new file mode 100644
index 0000000..e96e345
--- /dev/null
+++ b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/Olingo2ResponseHandler.java
@@ -0,0 +1,43 @@
+/**
+ * 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.camel.component.olingo2.api;
+
+/**
+ * Callback interface to asynchronously process Olingo2 response.
+ */
+public interface Olingo2ResponseHandler<T> {
+
+    /**
+     * Handle response data on successful completion of Olingo2 request.
+     * @param response response data from Olingo2, may be NULL for Olingo2 operations with no response data.
+     */
+    void onResponse(T response);
+
+    /**
+     * Handle exception raised from Olingo2 request.
+     * @param ex exception from Olingo2 request.
+     *           May be an instance of {@link org.apache.olingo.odata2.api.exception.ODataException} or
+     *           some other exception, such as {@link java.io.IOException}
+     */
+    void onException(Exception ex);
+
+    /**
+     * Handle Olingo2 request cancellation.
+     * May be caused by the underlying HTTP connection being shutdown asynchronously.
+     */
+    void onCanceled();
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Olingo2BatchChangeRequest.java
----------------------------------------------------------------------
diff --git a/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Olingo2BatchChangeRequest.java b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Olingo2BatchChangeRequest.java
new file mode 100644
index 0000000..d2d06ad
--- /dev/null
+++ b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Olingo2BatchChangeRequest.java
@@ -0,0 +1,94 @@
+/**
+ * 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.camel.component.olingo2.api.batch;
+
+import java.util.Map;
+
+/**
+ * Batch Change part.
+ */
+public class Olingo2BatchChangeRequest extends Olingo2BatchRequest {
+
+    protected String contentId;
+    protected Operation operation;
+    protected Object body;
+
+    public Operation getOperation() {
+        return operation;
+    }
+
+    public Object getBody() {
+        return body;
+    }
+
+    public String getContentId() {
+        return contentId;
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder("Batch Change Request{ ")
+            .append(resourcePath)
+            .append(", headers=").append(headers)
+            .append(", contentId=").append(contentId)
+            .append(", operation=").append(operation)
+            .append(", body=").append(body)
+            .append('}')
+            .toString();
+    }
+
+    public static Olingo2BatchChangeRequestBuilder resourcePath(String resourcePath) {
+        if (resourcePath == null) {
+            throw new IllegalArgumentException("resourcePath");
+        }
+        return new Olingo2BatchChangeRequestBuilder().resourcePath(resourcePath);
+    }
+
+    public static class Olingo2BatchChangeRequestBuilder {
+
+        private Olingo2BatchChangeRequest request = new Olingo2BatchChangeRequest();
+
+        public Olingo2BatchChangeRequestBuilder resourcePath(String resourcePath) {
+            request.resourcePath = resourcePath;
+            return this;
+        }
+
+        public Olingo2BatchChangeRequestBuilder headers(Map<String, String> headers) {
+            request.headers = headers;
+            return this;
+        }
+
+        public Olingo2BatchChangeRequestBuilder contentId(String contentId) {
+            request.contentId = contentId;
+            return this;
+        }
+
+        public Olingo2BatchChangeRequestBuilder operation(Operation operation) {
+            request.operation = operation;
+            return this;
+        }
+
+        public Olingo2BatchChangeRequestBuilder body(Object body) {
+            request.body = body;
+            return this;
+        }
+
+        public Olingo2BatchChangeRequest build() {
+            return request;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Olingo2BatchQueryRequest.java
----------------------------------------------------------------------
diff --git a/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Olingo2BatchQueryRequest.java b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Olingo2BatchQueryRequest.java
new file mode 100644
index 0000000..cac47ff
--- /dev/null
+++ b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Olingo2BatchQueryRequest.java
@@ -0,0 +1,71 @@
+/**
+ * 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.camel.component.olingo2.api.batch;
+
+import java.util.Map;
+
+/**
+ * Batch Query part.
+ */
+public class Olingo2BatchQueryRequest extends Olingo2BatchRequest {
+
+    private Map<String, String> queryParams;
+
+    public Map<String, String> getQueryParams() {
+        return queryParams;
+    }
+
+    public static Olingo2BatchQueryRequestBuilder resourcePath(String resourcePath) {
+        if (resourcePath == null) {
+            throw new IllegalArgumentException("resourcePath");
+        }
+        return new Olingo2BatchQueryRequestBuilder().resourcePath(resourcePath);
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder("Batch Query Request{ ")
+            .append(resourcePath)
+            .append(", headers=").append(headers)
+            .append(", queryParams=").append(queryParams)
+            .append('}')
+            .toString();
+    }
+
+    public static class Olingo2BatchQueryRequestBuilder {
+        private Olingo2BatchQueryRequest request = new Olingo2BatchQueryRequest();
+
+        public Olingo2BatchQueryRequest build() {
+            return request;
+        }
+
+        public Olingo2BatchQueryRequestBuilder resourcePath(String resourcePath) {
+            request.resourcePath = resourcePath;
+            return this;
+        }
+
+        public Olingo2BatchQueryRequestBuilder headers(Map<String, String> headers) {
+            request.headers = headers;
+            return this;
+        }
+
+        public Olingo2BatchQueryRequestBuilder queryParams(Map<String, String> queryParams) {
+            queryParams = queryParams;
+            return this;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Olingo2BatchRequest.java
----------------------------------------------------------------------
diff --git a/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Olingo2BatchRequest.java b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Olingo2BatchRequest.java
new file mode 100644
index 0000000..1da4ee2
--- /dev/null
+++ b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Olingo2BatchRequest.java
@@ -0,0 +1,39 @@
+/**
+ * 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.camel.component.olingo2.api.batch;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Base part in a multipart Batch request.
+ */
+public abstract class Olingo2BatchRequest {
+
+    protected String resourcePath;
+    protected Map<String, String> headers = new HashMap<String, String>();
+
+    public String getResourcePath() {
+        return resourcePath;
+    }
+
+    public Map<String, String> getHeaders() {
+        return headers;
+    }
+
+    public abstract String toString();
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Olingo2BatchResponse.java
----------------------------------------------------------------------
diff --git a/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Olingo2BatchResponse.java b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Olingo2BatchResponse.java
new file mode 100644
index 0000000..830ac46
--- /dev/null
+++ b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Olingo2BatchResponse.java
@@ -0,0 +1,65 @@
+/**
+ * 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.camel.component.olingo2.api.batch;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Batch Response part.
+ */
+public class Olingo2BatchResponse {
+
+    private final int statusCode;
+    private final String statusInfo;
+
+    private final String contentId;
+
+    private final Map<String, String> headers;
+    private final Object body;
+
+
+    public Olingo2BatchResponse(int statusCode, String statusInfo,
+                               String contentId, Map<String, String> headers, Object body) {
+        this.statusCode = statusCode;
+        this.statusInfo = statusInfo;
+        this.contentId = contentId;
+        this.headers = Collections.unmodifiableMap(new HashMap<String, String>(headers));
+        this.body = body;
+    }
+
+    public int getStatusCode() {
+        return statusCode;
+    }
+
+    public String getStatusInfo() {
+        return statusInfo;
+    }
+
+    public String getContentId() {
+        return contentId;
+    }
+
+    public Map<String, String> getHeaders() {
+        return headers;
+    }
+
+    public Object getBody() {
+        return body;
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Operation.java
----------------------------------------------------------------------
diff --git a/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Operation.java b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Operation.java
new file mode 100644
index 0000000..c08df52
--- /dev/null
+++ b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/batch/Operation.java
@@ -0,0 +1,35 @@
+/**
+ * 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.camel.component.olingo2.api.batch;
+
+/**
+* OData operation used by {@link org.apache.camel.component.olingo2.api.batch.Olingo2BatchChangeRequest}.
+*/
+public enum Operation {
+
+    CREATE("POST"), UPDATE("PUT"), PATCH("PATCH"), MERGE("MERGE"), DELETE("DELETE");
+
+    private final String httpMethod;
+
+    Operation(String httpMethod) {
+        this.httpMethod = httpMethod;
+    }
+
+    public String getHttpMethod() {
+        return httpMethod;
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/impl/AbstractFutureCallback.java
----------------------------------------------------------------------
diff --git a/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/impl/AbstractFutureCallback.java b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/impl/AbstractFutureCallback.java
new file mode 100644
index 0000000..04be21a
--- /dev/null
+++ b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/impl/AbstractFutureCallback.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.camel.component.olingo2.api.impl;
+
+import java.io.IOException;
+
+import org.apache.camel.component.olingo2.api.Olingo2ResponseHandler;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.concurrent.FutureCallback;
+import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
+import org.apache.olingo.odata2.api.ep.EntityProvider;
+import org.apache.olingo.odata2.api.ep.EntityProviderException;
+import org.apache.olingo.odata2.api.exception.ODataApplicationException;
+import org.apache.olingo.odata2.api.exception.ODataException;
+import org.apache.olingo.odata2.api.processor.ODataErrorContext;
+import org.apache.olingo.odata2.core.commons.ContentType;
+
+/**
+* Helper implementation of {@link org.apache.http.concurrent.FutureCallback}
+ * for {@link org.apache.camel.component.olingo2.api.impl.Olingo2AppImpl}
+*/
+public abstract class AbstractFutureCallback<T> implements FutureCallback<HttpResponse> {
+
+    private final Olingo2ResponseHandler<T> responseHandler;
+
+    AbstractFutureCallback(Olingo2ResponseHandler<T> responseHandler) {
+        this.responseHandler = responseHandler;
+    }
+
+    public static HttpStatusCodes checkStatus(HttpResponse response) throws ODataApplicationException {
+        final StatusLine statusLine = response.getStatusLine();
+        HttpStatusCodes httpStatusCode = HttpStatusCodes.fromStatusCode(statusLine.getStatusCode());
+        if (400 <= httpStatusCode.getStatusCode() && httpStatusCode.getStatusCode() <= 599) {
+            if (response.getEntity() != null) {
+                try {
+                    final ContentType responseContentType = ContentType.create(
+                        response.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue());
+
+                    switch (responseContentType.getODataFormat()) {
+                    case ATOM:
+                    case XML:
+                    case JSON:
+                        final ODataErrorContext errorContext = EntityProvider.readErrorDocument(
+                            response.getEntity().getContent(),
+                            responseContentType.toString());
+                        throw new ODataApplicationException(errorContext.getMessage(),
+                            errorContext.getLocale(), httpStatusCode, errorContext.getErrorCode(),
+                            errorContext.getException());
+                    default:
+                        // fall through to default exception with status line information
+                    }
+                } catch (EntityProviderException e) {
+                    throw new ODataApplicationException(e.getMessage(), response.getLocale(), httpStatusCode, e);
+                } catch (IOException e) {
+                    throw new ODataApplicationException(e.getMessage(), response.getLocale(), httpStatusCode, e);
+                }
+            }
+
+            throw new ODataApplicationException(statusLine.getReasonPhrase(), response.getLocale(), httpStatusCode);
+        }
+
+        return httpStatusCode;
+    }
+
+    @Override
+    public final void completed(HttpResponse result) {
+        try {
+            // check response status
+            checkStatus(result);
+
+            onCompleted(result);
+        } catch (Exception e) {
+            failed(e);
+        }
+    }
+
+    protected abstract void onCompleted(HttpResponse result) throws ODataException, IOException;
+
+    @Override
+    public final void failed(Exception ex) {
+        responseHandler.onException(ex);
+    }
+
+    @Override
+    public final void cancelled() {
+        responseHandler.onCanceled();
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/impl/HttpMerge.java
----------------------------------------------------------------------
diff --git a/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/impl/HttpMerge.java b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/impl/HttpMerge.java
new file mode 100644
index 0000000..b002b4e
--- /dev/null
+++ b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/impl/HttpMerge.java
@@ -0,0 +1,47 @@
+/**
+ * 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.camel.component.olingo2.api.impl;
+
+import java.net.URI;
+
+import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
+
+/**
+ * HTTP MERGE method.
+ */
+public class HttpMerge extends HttpEntityEnclosingRequestBase {
+
+    public static final String METHOD_NAME = "MERGE";
+
+    public HttpMerge() {
+    }
+
+    public HttpMerge(final URI uri) {
+        super();
+        setURI(uri);
+    }
+
+    public HttpMerge(final String uri) {
+        super();
+        setURI(URI.create(uri));
+    }
+
+    @Override
+    public String getMethod() {
+        return METHOD_NAME;
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/9a92064c/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/impl/Olingo2AppImpl.java
----------------------------------------------------------------------
diff --git a/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/impl/Olingo2AppImpl.java b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/impl/Olingo2AppImpl.java
new file mode 100644
index 0000000..125f7cf
--- /dev/null
+++ b/components/camel-olingo2/camel-olingo2-api/src/main/java/org/apache/camel/component/olingo2/api/impl/Olingo2AppImpl.java
@@ -0,0 +1,901 @@
+/**
+ * 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.camel.component.olingo2.api.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.camel.component.olingo2.api.Olingo2App;
+import org.apache.camel.component.olingo2.api.Olingo2ResponseHandler;
+import org.apache.camel.component.olingo2.api.batch.Olingo2BatchChangeRequest;
+import org.apache.camel.component.olingo2.api.batch.Olingo2BatchQueryRequest;
+import org.apache.camel.component.olingo2.api.batch.Olingo2BatchRequest;
+import org.apache.camel.component.olingo2.api.batch.Olingo2BatchResponse;
+import org.apache.camel.component.olingo2.api.batch.Operation;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpVersion;
+import org.apache.http.StatusLine;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPatch;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.concurrent.FutureCallback;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
+import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
+import org.apache.http.impl.nio.client.HttpAsyncClients;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.message.BasicStatusLine;
+import org.apache.http.nio.client.util.HttpAsyncClientUtils;
+import org.apache.olingo.odata2.api.ODataServiceVersion;
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.client.batch.BatchChangeSet;
+import org.apache.olingo.odata2.api.client.batch.BatchChangeSetPart;
+import org.apache.olingo.odata2.api.client.batch.BatchPart;
+import org.apache.olingo.odata2.api.client.batch.BatchQueryPart;
+import org.apache.olingo.odata2.api.client.batch.BatchSingleResponse;
+import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
+import org.apache.olingo.odata2.api.commons.ODataHttpHeaders;
+import org.apache.olingo.odata2.api.edm.Edm;
+import org.apache.olingo.odata2.api.edm.EdmEntitySet;
+import org.apache.olingo.odata2.api.edm.EdmException;
+import org.apache.olingo.odata2.api.edm.EdmProperty;
+import org.apache.olingo.odata2.api.ep.EntityProvider;
+import org.apache.olingo.odata2.api.ep.EntityProviderException;
+import org.apache.olingo.odata2.api.ep.EntityProviderReadProperties;
+import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties;
+import org.apache.olingo.odata2.api.exception.ODataApplicationException;
+import org.apache.olingo.odata2.api.exception.ODataException;
+import org.apache.olingo.odata2.api.processor.ODataResponse;
+import org.apache.olingo.odata2.api.uri.PathSegment;
+import org.apache.olingo.odata2.api.uri.UriParser;
+import org.apache.olingo.odata2.core.ODataPathSegmentImpl;
+import org.apache.olingo.odata2.core.commons.ContentType;
+import org.apache.olingo.odata2.core.uri.UriInfoImpl;
+
+/**
+ * Application API used by Olingo2 Component.
+ */
+public final class Olingo2AppImpl implements Olingo2App {
+
+    public static final ContentType APPLICATION_FORM_URL_ENCODED = ContentType.create("application", "x-www-form-urlencoded");
+
+    public static final String METADATA = "$metadata";
+
+    private static final String SEPARATOR = "/";
+
+    private static final String BOUNDARY_PREFIX = "batch_";
+    private static final String BOUNDARY_PARAMETER = "boundary";
+
+    private static final ContentType METADATA_CONTENT_TYPE = ContentType.APPLICATION_XML_CS_UTF_8;
+    private static final ContentType SERVICE_DOCUMENT_CONTENT_TYPE = ContentType.APPLICATION_ATOM_SVC_CS_UTF_8;
+    private static final ContentType BATCH_CONTENT_TYPE =
+        ContentType.MULTIPART_MIXED.receiveWithCharsetParameter(ContentType.CHARSET_UTF_8);
+    private static final String BATCH = "$batch";
+    private static final String MAX_DATA_SERVICE_VERSION = "Max" + ODataHttpHeaders.DATASERVICEVERSION;
+
+    private final CloseableHttpAsyncClient client;
+
+    private String serviceUri;
+    private ContentType contentType;
+    private Map<String, String> httpHeaders;
+
+    /**
+     * Create Olingo2 Application with default HTTP configuration.
+     */
+    public Olingo2AppImpl(String serviceUri) {
+        this(serviceUri, null);
+    }
+
+    /**
+     * Create Olingo2 Application with custom HTTP client builder.
+     *
+     * @param serviceUri Service Application base URI.
+     * @param builder custom HTTP client builder.
+     */
+    public Olingo2AppImpl(String serviceUri, HttpAsyncClientBuilder builder) {
+        if (serviceUri == null) {
+            throw new IllegalArgumentException("serviceUri");
+        }
+        this.serviceUri = serviceUri;
+
+        if (builder == null) {
+            this.client = HttpAsyncClients.createDefault();
+        } else {
+            this.client = builder.build();
+        }
+        this.client.start();
+        this.contentType = ContentType.APPLICATION_JSON_CS_UTF_8;
+    }
+
+    @Override
+    public void setServiceUri(String serviceUri) {
+        this.serviceUri = serviceUri;
+    }
+
+    @Override
+    public String getServiceUri() {
+        return serviceUri;
+    }
+
+    @Override
+    public Map<String, String> getHttpHeaders() {
+        return httpHeaders;
+    }
+
+    @Override
+    public void setHttpHeaders(Map<String, String> httpHeaders) {
+        this.httpHeaders = httpHeaders;
+    }
+
+    @Override
+    public String getContentType() {
+        return contentType.toString();
+    }
+
+    @Override
+    public void setContentType(String contentType) {
+        this.contentType = ContentType.parse(contentType);
+    }
+
+    @Override
+    public void close() {
+        HttpAsyncClientUtils.closeQuietly(client);
+    }
+
+    @Override
+    public <T> void read(final Edm edm, final String resourcePath, final Map<String, String> queryParams,
+                         final Olingo2ResponseHandler<T> responseHandler) {
+
+        final UriInfoImpl uriInfo = parseUri(edm, resourcePath, queryParams);
+
+        execute(new HttpGet(createUri(resourcePath, queryParams)), getResourceContentType(uriInfo),
+            new AbstractFutureCallback<T>(responseHandler) {
+
+                @Override
+                @SuppressWarnings("unchecked")
+                public void onCompleted(HttpResponse result) throws IOException {
+
+                    readContent(uriInfo, result.getEntity() != null ? result.getEntity().getContent() : null,
+                        responseHandler);
+                }
+
+            });
+    }
+
+    private ContentType getResourceContentType(UriInfoImpl uriInfo) {
+        ContentType resourceContentType;
+        switch (uriInfo.getUriType()) {
+        case URI0:
+            // service document
+            resourceContentType = SERVICE_DOCUMENT_CONTENT_TYPE;
+            break;
+        case URI8:
+            // metadata
+            resourceContentType = METADATA_CONTENT_TYPE;
+            break;
+        case URI4:
+        case URI5:
+            // is it a $value URI??
+            if (uriInfo.isValue()) {
+                // property value and $count
+                resourceContentType = ContentType.TEXT_PLAIN_CS_UTF_8;
+            } else {
+                resourceContentType = contentType;
+            }
+            break;
+        case URI15:
+        case URI16:
+        case URI50A:
+        case URI50B:
+            // $count
+            resourceContentType = ContentType.TEXT_PLAIN_CS_UTF_8;
+            break;
+        default:
+            resourceContentType = contentType;
+        }
+        return resourceContentType;
+    }
+
+    @Override
+    public <T> void create(Edm edm, String resourcePath, Object data, Olingo2ResponseHandler<T> responseHandler) {
+        final UriInfoImpl uriInfo = parseUri(edm, resourcePath, null);
+
+        writeContent(edm, new HttpPost(createUri(resourcePath, null)), uriInfo, data, responseHandler);
+    }
+
+    @Override
+    public <T> void update(Edm edm, String resourcePath, Object data, Olingo2ResponseHandler<T> responseHandler) {
+        final UriInfoImpl uriInfo = parseUri(edm, resourcePath, null);
+
+        writeContent(edm, new HttpPut(createUri(resourcePath, null)), uriInfo, data, responseHandler);
+    }
+
+    @Override
+    public <T> void patch(Edm edm, String resourcePath, Object data, Olingo2ResponseHandler<T> responseHandler) {
+        final UriInfoImpl uriInfo = parseUri(edm, resourcePath, null);
+
+        writeContent(edm, new HttpPatch(createUri(resourcePath, null)), uriInfo, data, responseHandler);
+    }
+
+    @Override
+    public <T> void merge(Edm edm, String resourcePath, Object data, Olingo2ResponseHandler<T> responseHandler) {
+        final UriInfoImpl uriInfo = parseUri(edm, resourcePath, null);
+
+        writeContent(edm, new HttpMerge(createUri(resourcePath, null)), uriInfo, data, responseHandler);
+    }
+
+    @Override
+    public void batch(Edm edm, Object data, Olingo2ResponseHandler<List<Olingo2BatchResponse>> responseHandler) {
+        final UriInfoImpl uriInfo = parseUri(edm, BATCH, null);
+
+        writeContent(edm, new HttpPost(createUri(BATCH, null)), uriInfo, data, responseHandler);
+    }
+
+    @Override
+    public void delete(String resourcePath, final Olingo2ResponseHandler<HttpStatusCodes> responseHandler) {
+
+        execute(new HttpDelete(createUri(resourcePath)), contentType,
+            new AbstractFutureCallback<HttpStatusCodes>(responseHandler) {
+                @Override
+                public void onCompleted(HttpResponse result) {
+                    final StatusLine statusLine = result.getStatusLine();
+                    responseHandler.onResponse(HttpStatusCodes.fromStatusCode(statusLine.getStatusCode()));
+                }
+            });
+    }
+
+    private <T> void readContent(UriInfoImpl uriInfo, InputStream content, Olingo2ResponseHandler<T> responseHandler) {
+        try {
+            responseHandler.onResponse(this.<T>readContent(uriInfo, content));
+        } catch (EntityProviderException e) {
+            responseHandler.onException(e);
+        } catch (ODataApplicationException e) {
+            responseHandler.onException(e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> T readContent(UriInfoImpl uriInfo, InputStream content)
+        throws EntityProviderException, ODataApplicationException {
+        T response;
+        switch (uriInfo.getUriType()) {
+        case URI0:
+            // service document
+            response = (T) EntityProvider.readServiceDocument(content, SERVICE_DOCUMENT_CONTENT_TYPE.toString());
+            break;
+
+        case URI8:
+            // $metadata
+            response = (T) EntityProvider.readMetadata(content, false);
+            break;
+
+        case URI7A:
+            // link
+            response = (T) EntityProvider.readLink(getContentType(), uriInfo.getTargetEntitySet(), content);
+            break;
+
+        case URI7B:
+            // links
+            response = (T) EntityProvider.readLinks(getContentType(), uriInfo.getTargetEntitySet(), content);
+            break;
+
+        case URI3:
+            // complex property
+            final List<EdmProperty> complexPropertyPath = uriInfo.getPropertyPath();
+            final EdmProperty complexProperty = complexPropertyPath.get(complexPropertyPath.size() - 1);
+            response = (T) EntityProvider.readProperty(getContentType(),
+                complexProperty, content, EntityProviderReadProperties.init().build());
+            break;
+
+        case URI4:
+        case URI5:
+            // simple property
+            final List<EdmProperty> simplePropertyPath = uriInfo.getPropertyPath();
+            final EdmProperty simpleProperty = simplePropertyPath.get(simplePropertyPath.size() - 1);
+            if (uriInfo.isValue()) {
+                response = (T) EntityProvider.readPropertyValue(simpleProperty, content);
+            } else {
+                response = (T) EntityProvider.readProperty(getContentType(),
+                    simpleProperty, content, EntityProviderReadProperties.init().build());
+            }
+            break;
+
+        case URI15:
+        case URI16:
+        case URI50A:
+        case URI50B:
+            // $count
+            try {
+                final String stringCount = new String(EntityProvider.readBinary(content), ContentType.CHARSET_UTF_8);
+                response = (T) Long.valueOf(stringCount);
+            } catch (UnsupportedEncodingException e) {
+                throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED, e);
+            }
+            break;
+
+        case URI1:
+        case URI6B:
+            if (uriInfo.getCustomQueryOptions().containsKey("!deltaToken")) {
+                // ODataDeltaFeed
+                response = (T) EntityProvider.readDeltaFeed(
+                    getContentType(),
+                    uriInfo.getTargetEntitySet(), content,
+                    EntityProviderReadProperties.init().build());
+            } else {
+                // ODataFeed
+                response = (T) EntityProvider.readFeed(
+                    getContentType(),
+                    uriInfo.getTargetEntitySet(), content,
+                    EntityProviderReadProperties.init().build());
+            }
+            break;
+
+        case URI2:
+        case URI6A:
+            response = (T) EntityProvider.readEntry(
+                getContentType(),
+                uriInfo.getTargetEntitySet(),
+                content,
+                EntityProviderReadProperties.init().build());
+            break;
+
+        default:
+            throw new ODataApplicationException("Unsupported resource type " + uriInfo.getTargetType(),
+                Locale.ENGLISH);
+        }
+
+        return response;
+    }
+
+    private <T> void writeContent(final Edm edm, HttpEntityEnclosingRequestBase httpEntityRequest,
+                                  final UriInfoImpl uriInfo, final Object content,
+                                  final Olingo2ResponseHandler<T> responseHandler) {
+
+        try {
+            // process resource by UriType
+            final ODataResponse response = writeContent(edm, uriInfo, content);
+
+            // copy all response headers
+            for (String header : response.getHeaderNames()) {
+                httpEntityRequest.setHeader(header, response.getHeader(header));
+            }
+
+            // get (http) entity which is for default Olingo2 implementation an InputStream
+            if (response.getEntity() instanceof InputStream) {
+                httpEntityRequest.setEntity(new InputStreamEntity((InputStream) response.getEntity()));
+/*
+                // avoid sending it without a header field set
+                if (!httpEntityRequest.containsHeader(HttpHeaders.CONTENT_TYPE)) {
+                    httpEntityRequest.addHeader(HttpHeaders.CONTENT_TYPE, getContentType());
+                }
+*/
+            }
+
+            // execute HTTP request
+            execute(httpEntityRequest, contentType, new AbstractFutureCallback<T>(responseHandler) {
+                @SuppressWarnings("unchecked")
+                @Override
+                public void onCompleted(HttpResponse result)
+                    throws IOException, EntityProviderException, BatchException, ODataApplicationException {
+
+                    // if a entity is created (via POST request) the response body contains the new created entity
+                    HttpStatusCodes statusCode = HttpStatusCodes.fromStatusCode(result.getStatusLine().getStatusCode());
+                    if (statusCode != HttpStatusCodes.NO_CONTENT) {
+
+                        // TODO do we need to handle response based on other UriTypes???
+                        switch (uriInfo.getUriType()) {
+                        case URI9:
+                            // $batch
+                            final List<BatchSingleResponse> singleResponses = EntityProvider.parseBatchResponse(
+                                result.getEntity().getContent(),
+                                result.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue());
+
+                            // parse batch response bodies
+                            final List<Olingo2BatchResponse> responses = new ArrayList<Olingo2BatchResponse>();
+                            Map<String, String> contentIdLocationMap = new HashMap<String, String>();
+
+                            final List<Olingo2BatchRequest> batchRequests = (List<Olingo2BatchRequest>) content;
+                            final Iterator<Olingo2BatchRequest> iterator = batchRequests.iterator();
+
+                            for (BatchSingleResponse response : singleResponses) {
+                                final Olingo2BatchRequest request = iterator.next();
+
+                                if (request instanceof Olingo2BatchChangeRequest
+                                    && ((Olingo2BatchChangeRequest)request).getContentId() != null) {
+
+                                    contentIdLocationMap.put("$" + ((Olingo2BatchChangeRequest)request).getContentId(),
+                                        response.getHeader(HttpHeaders.LOCATION));
+                                }
+
+                                try {
+                                    responses.add(parseResponse(edm, contentIdLocationMap, request, response));
+                                } catch (Exception e) {
+                                    // report any parsing errors as error response
+                                    responses.add(new Olingo2BatchResponse(
+                                        Integer.parseInt(response.getStatusCode()),
+                                        response.getStatusInfo(), response.getContentId(), response.getHeaders(),
+                                        new ODataApplicationException(
+                                            "Error parsing response for " + request + ": " + e.getMessage(),
+                                            Locale.ENGLISH, e)));
+                                }
+                            }
+                            responseHandler.onResponse((T) responses);
+                            break;
+
+                        default:
+                            // get the response content as an ODataEntry object
+                            responseHandler.onResponse((T) EntityProvider.readEntry(response.getContentHeader(),
+                                uriInfo.getTargetEntitySet(),
+                                result.getEntity().getContent(),
+                                EntityProviderReadProperties.init().build()));
+                            break;
+                        }
+
+                    } else {
+                        responseHandler.onResponse(
+                            (T) HttpStatusCodes.fromStatusCode(result.getStatusLine().getStatusCode()));
+                    }
+                }
+            });
+        } catch (ODataException e) {
+            responseHandler.onException(e);
+        } catch (URISyntaxException e) {
+            responseHandler.onException(e);
+        } catch (UnsupportedEncodingException e) {
+            responseHandler.onException(e);
+        } catch (IOException e) {
+            responseHandler.onException(e);
+        }
+    }
+
+    private ODataResponse writeContent(Edm edm, UriInfoImpl uriInfo, Object content)
+        throws ODataApplicationException, EdmException, EntityProviderException, URISyntaxException, IOException {
+
+        String responseContentType = getContentType();
+        ODataResponse response;
+
+        switch (uriInfo.getUriType()) {
+        case URI4:
+        case URI5:
+            // simple property
+            final List<EdmProperty> simplePropertyPath = uriInfo.getPropertyPath();
+            final EdmProperty simpleProperty = simplePropertyPath.get(simplePropertyPath.size() - 1);
+            responseContentType = simpleProperty.getMimeType();
+            if (uriInfo.isValue()) {
+                response = EntityProvider.writePropertyValue(simpleProperty, content);
+                responseContentType = ContentType.TEXT_PLAIN_CS_UTF_8.toString();
+            } else {
+                response = EntityProvider.writeProperty(getContentType(), simpleProperty, content);
+            }
+            break;
+
+        case URI3:
+            // complex property
+            final List<EdmProperty> complexPropertyPath = uriInfo.getPropertyPath();
+            final EdmProperty complexProperty = complexPropertyPath.get(complexPropertyPath.size() - 1);
+            response = EntityProvider.writeProperty(responseContentType, complexProperty, content);
+            break;
+
+        case URI7A:
+            // $links with 0..1 cardinality property
+            final EdmEntitySet targetLinkEntitySet = uriInfo.getTargetEntitySet();
+            final URI rootLinkUri = new URI(targetLinkEntitySet.getName());
+            EntityProviderWriteProperties linkProperties =
+                EntityProviderWriteProperties.serviceRoot(rootLinkUri).build();
+            @SuppressWarnings("unchecked")
+            final Map<String, Object> linkMap = (Map<String, Object>) content;
+            response = EntityProvider.writeLink(responseContentType, targetLinkEntitySet, linkMap, linkProperties);
+            break;
+
+        case URI7B:
+            // $links with * cardinality property
+            final EdmEntitySet targetLinksEntitySet = uriInfo.getTargetEntitySet();
+            final URI rootLinksUri = new URI(targetLinksEntitySet.getName());
+            EntityProviderWriteProperties linksProperties =
+                EntityProviderWriteProperties.serviceRoot(rootLinksUri).build();
+            @SuppressWarnings("unchecked")
+            final Map<String, Object> linksMap = (Map<String, Object>) content;
+            response = EntityProvider.writeLink(responseContentType, targetLinksEntitySet, linksMap, linksProperties);
+            break;
+
+        case URI1:
+        case URI2:
+        case URI6A:
+        case URI6B:
+            // Entity
+            final EdmEntitySet targetEntitySet = uriInfo.getTargetEntitySet();
+            final URI rootUri = new URI(targetEntitySet.getName());
+            EntityProviderWriteProperties properties = EntityProviderWriteProperties.serviceRoot(rootUri).build();
+            @SuppressWarnings("unchecked")
+            final Map<String, Object> objectMap = (Map<String, Object>) content;
+            response = EntityProvider.writeEntry(responseContentType, targetEntitySet, objectMap, properties);
+            break;
+
+        case URI9:
+            // $batch
+            @SuppressWarnings("unchecked")
+            final List<Olingo2BatchRequest> batchParts = (List<Olingo2BatchRequest>) content;
+            response = parseBatchRequest(edm, batchParts);
+            break;
+
+        default:
+            // notify exception and return!!!
+            throw new ODataApplicationException("Unsupported resource type " + uriInfo.getTargetType(),
+                Locale.ENGLISH);
+        }
+
+        return response.getContentHeader() != null ? response
+            : ODataResponse.fromResponse(response).contentHeader(responseContentType).build();
+    }
+
+    private ODataResponse parseBatchRequest(final Edm edm, final List<Olingo2BatchRequest> batchParts)
+        throws IOException, EntityProviderException, ODataApplicationException, EdmException, URISyntaxException {
+
+        // create Batch request from parts
+        final ArrayList<BatchPart> parts = new ArrayList<BatchPart>();
+        final ArrayList<BatchChangeSetPart> changeSetParts = new ArrayList<BatchChangeSetPart>();
+
+        final Map<String, String> contentIdMap = new HashMap<String, String>();
+
+        for (Olingo2BatchRequest batchPart : batchParts) {
+
+            if (batchPart instanceof Olingo2BatchQueryRequest) {
+
+                // need to add change set parts collected so far??
+                if (!changeSetParts.isEmpty()) {
+                    addChangeSetParts(parts, changeSetParts);
+                    changeSetParts.clear();
+                    contentIdMap.clear();
+                }
+
+                // add to request parts
+                final UriInfoImpl uriInfo = parseUri(edm, batchPart.getResourcePath(), null);
+                parts.add(createBatchQueryPart(uriInfo, (Olingo2BatchQueryRequest) batchPart));
+
+            } else {
+
+                // add to change set parts
+                final BatchChangeSetPart changeSetPart = createBatchChangeSetPart(
+                    edm, contentIdMap, (Olingo2BatchChangeRequest) batchPart);
+                changeSetParts.add(changeSetPart);
+            }
+        }
+
+        // add any remaining change set parts
+        if (!changeSetParts.isEmpty()) {
+            addChangeSetParts(parts, changeSetParts);
+        }
+
+        final String boundary = BOUNDARY_PREFIX + UUID.randomUUID();
+        InputStream batchRequest = EntityProvider.writeBatchRequest(parts, boundary);
+        // add two blank lines before all --batch boundaries
+        // otherwise Olingo2 EntityProvider parser barfs in the server!!!
+        final byte[] bytes = EntityProvider.readBinary(batchRequest);
+        final String batchRequestBody = new String(bytes, ContentType.CHARSET_UTF_8);
+        batchRequest = new ByteArrayInputStream(batchRequestBody.replaceAll(
+            "--(batch_)", "\r\n\r\n--$1").getBytes(ContentType.CHARSET_UTF_8));
+
+        final String contentHeader = ContentType.create(BATCH_CONTENT_TYPE, BOUNDARY_PARAMETER, boundary).toString();
+        return ODataResponse.entity(batchRequest).contentHeader(contentHeader).build();
+    }
+
+    private void addChangeSetParts(ArrayList<BatchPart> parts, ArrayList<BatchChangeSetPart> changeSetParts) {
+        final BatchChangeSet changeSet = BatchChangeSet.newBuilder().build();
+        for (BatchChangeSetPart changeSetPart : changeSetParts) {
+            changeSet.add(changeSetPart);
+        }
+        parts.add(changeSet);
+    }
+
+    private BatchChangeSetPart createBatchChangeSetPart(Edm edm, Map<String, String> contentIdMap,
+                                                        Olingo2BatchChangeRequest batchRequest)
+        throws EdmException, URISyntaxException, EntityProviderException, IOException, ODataApplicationException {
+
+        // build body string
+        String resourcePath = batchRequest.getResourcePath();
+        // is it a referenced entity?
+        if (resourcePath.startsWith("$")) {
+            resourcePath = replaceContentId(edm, resourcePath, contentIdMap);
+        }
+
+        final UriInfoImpl uriInfo = parseUri(edm, resourcePath, null);
+
+        // serialize data into ODataResponse object, if set in request and this is not a DELETE request
+        final Map<String, String> headers = new HashMap<String, String>();
+        byte[] body = null;
+
+        if (batchRequest.getBody() != null
+            && !Operation.DELETE.equals(batchRequest.getOperation())) {
+
+            final ODataResponse response = writeContent(edm, uriInfo, batchRequest.getBody());
+            // copy response headers
+            for (String header : response.getHeaderNames()) {
+                headers.put(header, response.getHeader(header));
+            }
+
+            // get (http) entity which is for default Olingo2 implementation an InputStream
+            body = response.getEntity() instanceof InputStream
+                ? EntityProvider.readBinary((InputStream) response.getEntity()) : null;
+            if (body != null) {
+                headers.put(HttpHeaders.CONTENT_LENGTH, String.valueOf(body.length));
+            }
+        }
+
+        headers.put(HttpHeaders.ACCEPT, getResourceContentType(uriInfo).toString());
+        if (!headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
+            headers.put(HttpHeaders.CONTENT_TYPE, getContentType());
+        }
+
+        // add request headers
+        headers.putAll(batchRequest.getHeaders());
+
+        final String contentId = batchRequest.getContentId();
+        if (contentId != null) {
+            contentIdMap.put("$" + contentId, resourcePath);
+        }
+        return BatchChangeSetPart.uri(createBatchUri(batchRequest))
+            .method(batchRequest.getOperation().getHttpMethod())
+            .contentId(contentId)
+            .headers(headers)
+            .body(body == null ? null : new String(body, ContentType.CHARSET_UTF_8)).build();
+    }
+
+    private BatchQueryPart createBatchQueryPart(UriInfoImpl uriInfo, Olingo2BatchQueryRequest batchRequest) {
+
+        final Map<String, String> headers = new HashMap<String, String>(batchRequest.getHeaders());
+        if (!headers.containsKey(HttpHeaders.ACCEPT)) {
+            headers.put(HttpHeaders.ACCEPT, getResourceContentType(uriInfo).toString());
+        }
+
+        return BatchQueryPart.method("GET")
+            .uri(createBatchUri(batchRequest))
+            .headers(headers)
+            .build();
+    }
+
+    private static String replaceContentId(Edm edm, String entityReference, Map<String, String> contentIdMap) throws EdmException {
+        final int pathSeparator = entityReference.indexOf('/');
+        final StringBuilder referencedEntity;
+        if (pathSeparator == -1) {
+            referencedEntity = new StringBuilder(contentIdMap.get(entityReference));
+        } else {
+            referencedEntity = new StringBuilder(contentIdMap.get(entityReference.substring(0, pathSeparator)));
+        }
+
+        // create a dummy entity location by adding a dummy key predicate
+        final EdmEntitySet entitySet = edm.getDefaultEntityContainer().getEntitySet(referencedEntity.toString());
+        final List<EdmProperty> keyProperties = entitySet.getEntityType().getKeyProperties();
+
+        if (keyProperties.size() == 1) {
+            referencedEntity.append("('dummy')");
+        } else {
+            referencedEntity.append("(");
+            for (EdmProperty keyProperty : keyProperties) {
+                referencedEntity.append(keyProperty.getName()).append('=').append("'dummy',");
+            }
+            referencedEntity.deleteCharAt(referencedEntity.length() - 1);
+            referencedEntity.append(')');
+        }
+
+        return pathSeparator == -1 ? referencedEntity.toString()
+            : referencedEntity.append(entityReference.substring(pathSeparator)).toString();
+    }
+
+    private Olingo2BatchResponse parseResponse(Edm edm, Map<String, String> contentIdLocationMap,
+                                              Olingo2BatchRequest request, BatchSingleResponse response)
+        throws EntityProviderException, ODataApplicationException {
+
+        // validate HTTP status
+        final int statusCode = Integer.parseInt(response.getStatusCode());
+        final String statusInfo = response.getStatusInfo();
+
+        final BasicHttpResponse httpResponse = new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1,
+            statusCode, statusInfo));
+        final Map<String, String> headers = response.getHeaders();
+        for (Map.Entry<String, String> entry : headers.entrySet()) {
+            httpResponse.setHeader(entry.getKey(), entry.getValue());
+        }
+
+        ByteArrayInputStream content = null;
+        try {
+            if (response.getBody() != null) {
+                final ContentType partContentType = ContentType.create(
+                    headers.get(HttpHeaders.CONTENT_TYPE)).receiveWithCharsetParameter(ContentType.CHARSET_UTF_8);
+                final String charset = partContentType.getParameters().get(ContentType.PARAMETER_CHARSET);
+
+                final String body = response.getBody();
+                content = body != null ? new ByteArrayInputStream(body.getBytes(charset)) : null;
+
+                httpResponse.setEntity(new StringEntity(body, charset));
+            }
+
+            AbstractFutureCallback.checkStatus(httpResponse);
+        } catch (ODataApplicationException e) {
+            return new Olingo2BatchResponse(
+                statusCode, statusInfo, response.getContentId(),
+                response.getHeaders(), e);
+        } catch (UnsupportedEncodingException e) {
+            return new Olingo2BatchResponse(
+                statusCode, statusInfo, response.getContentId(),
+                response.getHeaders(), e);
+        }
+
+        // resolve resource path and query params and parse batch part uri
+        final String resourcePath = request.getResourcePath();
+        final String resolvedResourcePath;
+        if (resourcePath.startsWith("$") && !(METADATA.equals(resourcePath) || BATCH.equals(resourcePath))) {
+            resolvedResourcePath = findLocation(resourcePath, contentIdLocationMap);
+        } else {
+            final String resourceLocation = response.getHeader(HttpHeaders.LOCATION);
+            resolvedResourcePath = resourceLocation != null
+                ? resourceLocation.substring(serviceUri.length()) : resourcePath;
+        }
+        final Map<String, String> resolvedQueryParams = request instanceof Olingo2BatchQueryRequest
+            ? ((Olingo2BatchQueryRequest) request).getQueryParams() : null;
+        final UriInfoImpl uriInfo = parseUri(edm, resolvedResourcePath, resolvedQueryParams);
+
+        // resolve response content
+        final Object resolvedContent = content != null ? readContent(uriInfo, content) : null;
+
+        return new Olingo2BatchResponse(statusCode, statusInfo, response.getContentId(), response.getHeaders(),
+            resolvedContent);
+    }
+
+    private String findLocation(String resourcePath, Map<String, String> contentIdLocationMap) {
+        final int pathSeparator = resourcePath.indexOf('/');
+        if (pathSeparator == -1) {
+            return contentIdLocationMap.get(resourcePath);
+        } else {
+            return contentIdLocationMap.get(resourcePath.substring(0, pathSeparator))
+                + resourcePath.substring(pathSeparator);
+        }
+
+    }
+
+    private String createBatchUri(Olingo2BatchRequest part) {
+        String result;
+        if (part instanceof Olingo2BatchQueryRequest) {
+            final Olingo2BatchQueryRequest queryPart = (Olingo2BatchQueryRequest) part;
+            result = createUri(queryPart.getResourcePath(), queryPart.getQueryParams());
+        } else {
+            result = createUri(part.getResourcePath());
+        }
+        // strip base URI
+        return result.substring(serviceUri.length() + 1);
+    }
+
+    private String createUri(String resourcePath) {
+        return createUri(resourcePath, null);
+    }
+
+    private String createUri(String resourcePath, Map<String, String> queryParams) {
+
+        final StringBuilder absolutUri = new StringBuilder(serviceUri).append(SEPARATOR).append(resourcePath);
+        if (queryParams != null && !queryParams.isEmpty()) {
+            absolutUri.append("/?");
+            int nParams = queryParams.size();
+            int index = 0;
+            for (Map.Entry<String, String> entry : queryParams.entrySet()) {
+                absolutUri.append(entry.getKey()).append('=').append(entry.getValue());
+                if (++index < nParams) {
+                    absolutUri.append('&');
+                }
+            }
+        }
+        return absolutUri.toString();
+    }
+
+    private static UriInfoImpl parseUri(Edm edm, String resourcePath, Map<String, String> queryParams) {
+        UriInfoImpl result;
+        try {
+            final List<PathSegment> pathSegments = new ArrayList<PathSegment>();
+            final String[] segments = new URI(resourcePath).getPath().split(SEPARATOR);
+            if (queryParams == null) {
+                queryParams = Collections.emptyMap();
+            }
+            for (String segment : segments) {
+                if (segment.indexOf(';') == -1) {
+
+                    pathSegments.add(new ODataPathSegmentImpl(segment, null));
+                } else {
+
+                    // handle matrix params in path segment
+                    final String[] splitSegment = segment.split(";");
+                    segment = splitSegment[0];
+
+                    Map<String, List<String>> matrixParams = new HashMap<String, List<String>>();
+                    for (int i = 1; i < splitSegment.length; i++) {
+                        final String[] param = splitSegment[i].split("=");
+                        List<String> values = matrixParams.get(param[0]);
+                        if (values == null) {
+                            values = new ArrayList<String>();
+                            matrixParams.put(param[0], values);
+                        }
+                        if (param[1].indexOf(',') == -1) {
+                            values.add(param[1]);
+                        } else {
+                            values.addAll(Arrays.asList(param[1].split(",")));
+                        }
+                    }
+                    pathSegments.add(new ODataPathSegmentImpl(segment, matrixParams));
+                }
+            }
+            result = (UriInfoImpl) UriParser.parse(edm, pathSegments, queryParams);
+        } catch (URISyntaxException e) {
+            throw new IllegalArgumentException("resourcePath: " + e.getMessage(), e);
+        } catch (ODataException e) {
+            throw new IllegalArgumentException("resourcePath: " + e.getMessage(), e);
+        }
+
+        return result;
+    }
+
+    // public for unit test, not to be used otherwise
+    public void execute(HttpUriRequest httpUriRequest, ContentType contentType,
+                        FutureCallback<HttpResponse> callback) {
+
+        // add accept header when its not a form or multipart
+        final String contentTypeString = contentType.toString();
+        if (!APPLICATION_FORM_URL_ENCODED.equals(contentType)
+            && !contentType.getType().equals(ContentType.MULTIPART_MIXED.getType())) {
+            // otherwise accept what is being sent
+            httpUriRequest.addHeader(HttpHeaders.ACCEPT, contentTypeString);
+        }
+        // is something being sent?
+        if (httpUriRequest instanceof HttpEntityEnclosingRequestBase) {
+            httpUriRequest.addHeader(HttpHeaders.CONTENT_TYPE, contentTypeString);
+        }
+
+        // set user specified custom headers
+        if (httpHeaders != null && !httpHeaders.isEmpty()) {
+            for (Map.Entry<String, String> entry : httpHeaders.entrySet()) {
+                httpUriRequest.setHeader(entry.getKey(), entry.getValue());
+            }
+        }
+
+        // add client protocol version if not specified
+        if (!httpUriRequest.containsHeader(ODataHttpHeaders.DATASERVICEVERSION)) {
+            httpUriRequest.addHeader(ODataHttpHeaders.DATASERVICEVERSION, ODataServiceVersion.V20);
+        }
+        if (!httpUriRequest.containsHeader(MAX_DATA_SERVICE_VERSION)) {
+            httpUriRequest.addHeader(MAX_DATA_SERVICE_VERSION, ODataServiceVersion.V30);
+        }
+
+        // execute request
+        client.execute(httpUriRequest, callback);
+    }
+
+}