CAMEL-11056: Create new camel-olingo4 component for supporting OData 4.0

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

Branch: refs/heads/master
Commit: e6eded4c217b1734f36731396bff2f7fb9ee6f04
Parents: 46180b9
Author: Dmitry Volodin <dmvo...@gmail.com>
Authored: Thu Mar 23 15:48:32 2017 +0300
Committer: Andrea Cosentino <anco...@gmail.com>
Committed: Fri Mar 24 09:49:39 2017 +0100

----------------------------------------------------------------------
 .../camel-olingo4/camel-olingo4-api/pom.xml     | 141 ++++
 .../camel/component/olingo4/api/Olingo4App.java | 145 ++++
 .../olingo4/api/Olingo4ResponseHandler.java     |  43 +
 .../api/batch/Olingo4BatchChangeRequest.java    | 103 +++
 .../api/batch/Olingo4BatchQueryRequest.java     |  76 ++
 .../olingo4/api/batch/Olingo4BatchRequest.java  |  44 +
 .../olingo4/api/batch/Olingo4BatchResponse.java |  63 ++
 .../component/olingo4/api/batch/Operation.java  |  39 +
 .../api/impl/AbstractFutureCallback.java        | 111 +++
 .../olingo4/api/impl/Olingo4AppImpl.java        | 841 +++++++++++++++++++
 .../src/main/resources/META-INF/LICENSE.txt     | 203 +++++
 .../src/main/resources/META-INF/NOTICE.txt      |  11 +
 .../component/olingo4/Olingo4AppAPITest.java    | 543 ++++++++++++
 .../src/test/resources/log4j2.properties        |  28 +
 .../camel-olingo4-component/pom.xml             | 191 +++++
 .../src/main/docs/olingo4-component.adoc        | 252 ++++++
 .../src/main/java/META-INF/MANIFEST.MF          |   3 +
 .../component/olingo4/Olingo4AppWrapper.java    | 108 +++
 .../component/olingo4/Olingo4Component.java     | 184 ++++
 .../component/olingo4/Olingo4Configuration.java | 221 +++++
 .../component/olingo4/Olingo4Consumer.java      |  92 ++
 .../component/olingo4/Olingo4Endpoint.java      | 222 +++++
 .../component/olingo4/Olingo4Producer.java      | 106 +++
 .../olingo4/internal/Olingo4Constants.java      |  29 +
 .../internal/Olingo4PropertiesHelper.java       |  39 +
 .../src/main/resources/META-INF/LICENSE.txt     | 203 +++++
 .../src/main/resources/META-INF/NOTICE.txt      |  11 +
 .../services/org/apache/camel/component/olingo4 |  18 +
 .../src/signatures/olingo-api-signature.txt     |   8 +
 .../olingo4/AbstractOlingo4TestSupport.java     | 104 +++
 .../component/olingo4/Olingo4ComponentTest.java | 264 ++++++
 .../src/test/resources/log4j2.properties        |  28 +
 components/camel-olingo4/pom.xml                |  38 +
 components/pom.xml                              |   1 +
 parent/pom.xml                                  |   1 +
 .../camel-olingo4-starter/pom.xml               |  67 ++
 .../Olingo4ComponentAutoConfiguration.java      | 111 +++
 .../Olingo4ComponentConfiguration.java          | 201 +++++
 .../src/main/resources/META-INF/LICENSE.txt     | 203 +++++
 .../src/main/resources/META-INF/NOTICE.txt      |  11 +
 ...dditional-spring-configuration-metadata.json |  10 +
 .../main/resources/META-INF/spring.factories    |  19 +
 .../src/main/resources/META-INF/spring.provides |  18 +
 .../spring-boot/components-starter/pom.xml      |   1 +
 44 files changed, 5155 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/e6eded4c/components/camel-olingo4/camel-olingo4-api/pom.xml
----------------------------------------------------------------------
diff --git a/components/camel-olingo4/camel-olingo4-api/pom.xml 
b/components/camel-olingo4/camel-olingo4-api/pom.xml
new file mode 100644
index 0000000..7f0495c
--- /dev/null
+++ b/components/camel-olingo4/camel-olingo4-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</groupId>
+    <artifactId>camel-olingo4-parent</artifactId>
+    <version>2.19.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>camel-olingo4-api</artifactId>
+  <name>Camel :: Olingo4 :: API</name>
+  <description>Camel Olingo4 API</description>
+  <packaging>jar</packaging>
+
+  <properties>
+    
<camel.osgi.export.pkg>org.apache.camel.component.olingo4.api*</camel.osgi.export.pkg>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.olingo</groupId>
+      <artifactId>odata-commons-api</artifactId>
+      <version>${olingo4-version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.olingo</groupId>
+      <artifactId>odata-commons-core</artifactId>
+      <version>${olingo4-version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.olingo</groupId>
+      <artifactId>odata-client-core</artifactId>
+      <version>${olingo4-version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.olingo</groupId>
+      <artifactId>odata-server-core</artifactId>
+      <version>${olingo4-version}</version>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpasyncclient</artifactId>
+    </dependency>
+    
+    <dependency>
+      <groupId>net.sf.jsignature.io-tools</groupId>
+      <artifactId>easystream</artifactId>
+      <version>1.2.15</version>
+    </dependency>
+
+    <!-- logging -->
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-api</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-slf4j-impl</artifactId>
+      <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>
+        <executions>
+          <execution>
+            <id>add-javadoc</id>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+            <configuration>
+              <attach>true</attach>
+              <source>${jdk.version}</source>
+              <quiet>true</quiet>
+              <detectOfflineLinks>false</detectOfflineLinks>
+              <javadocVersion>${jdk.version}</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/e6eded4c/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/Olingo4App.java
----------------------------------------------------------------------
diff --git 
a/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/Olingo4App.java
 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/Olingo4App.java
new file mode 100644
index 0000000..95d42c2
--- /dev/null
+++ 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/Olingo4App.java
@@ -0,0 +1,145 @@
+/**
+ * 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.olingo4.api;
+
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.component.olingo4.api.batch.Olingo4BatchResponse;
+import org.apache.olingo.commons.api.edm.Edm;
+import org.apache.olingo.commons.api.http.HttpStatusCode;
+
+/**
+ * Olingo4 Client Api Interface.
+ */
+public interface Olingo4App {
+
+    /**
+     * 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
+     *                    
http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part1-protocol.html#_Toc453752288
+     * @param responseHandler callback handler
+     */
+    <T> void read(Edm edm, String resourcePath, Map<String, String> 
queryParams, Olingo4ResponseHandler<T> responseHandler);
+
+    /**
+     * Reads an OData resource and invokes callback with the unparsed input 
stream.
+     * @param edm Service Edm, read from calling <code>read(null, "$metdata", 
null, responseHandler)</code>
+     * @param resourcePath OData Resource path
+     * @param queryParams OData query params
+     *                    
http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part1-protocol.html#_Toc453752288
+     * @param responseHandler callback handler
+     */
+    void uread(Edm edm, String resourcePath, Map<String, String> queryParams,
+               Olingo4ResponseHandler<InputStream> responseHandler);
+
+    /**
+     * Deletes an OData resource and invokes callback
+     * with {@link org.apache.olingo.commons.api.http.HttpStatusCode} on 
success, or with exception on failure.
+     * @param resourcePath resource path for Entry
+     * @param responseHandler {@link 
org.apache.olingo.commons.api.http.HttpStatusCode} callback handler
+     */
+    void delete(String resourcePath, Olingo4ResponseHandler<HttpStatusCode> 
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, 
Olingo4ResponseHandler<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.client.api.domain.ClientEntity} callback handler
+     */
+    <T> void update(Edm edm, String resourcePath, Object data, 
Olingo4ResponseHandler<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.client.api.domain.ClientEntity} callback handler
+     */
+    <T> void patch(Edm edm, String resourcePath, Object data, 
Olingo4ResponseHandler<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.client.api.domain.ClientEntity} callback handler
+     */
+    <T> void merge(Edm edm, String resourcePath, Object data, 
Olingo4ResponseHandler<T> responseHandler);
+
+    /**
+     * Executes a batch request.
+     * @param edm service Edm
+     * @param data ordered {@link 
org.apache.camel.component.olingo4.api.batch.Olingo4BatchRequest} list
+     * @param responseHandler callback handler
+     */
+    void batch(Edm edm, Object data, 
Olingo4ResponseHandler<List<Olingo4BatchResponse>> responseHandler);
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/e6eded4c/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/Olingo4ResponseHandler.java
----------------------------------------------------------------------
diff --git 
a/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/Olingo4ResponseHandler.java
 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/Olingo4ResponseHandler.java
new file mode 100644
index 0000000..13438ba
--- /dev/null
+++ 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/Olingo4ResponseHandler.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.olingo4.api;
+
+/**
+ * Callback interface to asynchronously process Olingo4 response.
+ */
+public interface Olingo4ResponseHandler<T> {
+
+    /**
+     * Handle response data on successful completion of Olingo4 request.
+     * @param response response data from Olingo4, may be NULL for Olingo4 
operations with no response data.
+     */
+    void onResponse(T response);
+
+    /**
+     * Handle exception raised from Olingo4 request.
+     * @param ex exception from Olingo4 request.
+     * May be an instance of {@link 
org.apache.olingo.commons.api.ex.ODataException} or
+     * some other exception, such as {@link java.io.IOException}
+     */
+    void onException(Exception ex);
+
+    /**
+     * Handle Olingo4 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/e6eded4c/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Olingo4BatchChangeRequest.java
----------------------------------------------------------------------
diff --git 
a/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Olingo4BatchChangeRequest.java
 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Olingo4BatchChangeRequest.java
new file mode 100644
index 0000000..5b4e1eb
--- /dev/null
+++ 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Olingo4BatchChangeRequest.java
@@ -0,0 +1,103 @@
+/**
+ * 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.olingo4.api.batch;
+
+import java.util.Map;
+
+/**
+ * Batch Change part.
+ */
+public class Olingo4BatchChangeRequest extends Olingo4BatchRequest {
+
+    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(resourceUri).append("/").append(resourcePath).append(", 
headers=").append(headers).append(", contentId=")
+            .append(contentId).append(", 
operation=").append(operation).append(", 
body=").append(body).append('}').toString();
+    }
+
+    public static Olingo4BatchChangeRequestBuilder resourcePath(String 
resourcePath) {
+        if (resourcePath == null) {
+            throw new IllegalArgumentException("resourcePath");
+        }
+        return new 
Olingo4BatchChangeRequestBuilder().resourcePath(resourcePath);
+    }
+
+    public static class Olingo4BatchChangeRequestBuilder {
+
+        private Olingo4BatchChangeRequest request = new 
Olingo4BatchChangeRequest();
+
+        public Olingo4BatchChangeRequestBuilder resourcePath(String 
resourcePath) {
+            request.resourcePath = resourcePath;
+            return this;
+        }
+
+        public Olingo4BatchChangeRequestBuilder resourceUri(String 
resourceUri) {
+            request.resourceUri = resourceUri;
+            return this;
+        }
+
+        public Olingo4BatchChangeRequestBuilder headers(Map<String, String> 
headers) {
+            request.headers = headers;
+            return this;
+        }
+
+        public Olingo4BatchChangeRequestBuilder contentId(String contentId) {
+            request.contentId = contentId;
+            return this;
+        }
+
+        public Olingo4BatchChangeRequestBuilder operation(Operation operation) 
{
+            request.operation = operation;
+            return this;
+        }
+
+        public Olingo4BatchChangeRequestBuilder body(Object body) {
+            request.body = body;
+            return this;
+        }
+
+        public Olingo4BatchChangeRequest build() {
+            // avoid later NPEs
+            if (request.resourcePath == null) {
+                throw new IllegalArgumentException("Null resourcePath");
+            }
+            if (request.operation == null) {
+                throw new IllegalArgumentException("Null operation");
+            }
+            if (request.operation != Operation.DELETE && request.body == null) 
{
+                throw new IllegalArgumentException("Null body");
+            }
+            return request;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/e6eded4c/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Olingo4BatchQueryRequest.java
----------------------------------------------------------------------
diff --git 
a/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Olingo4BatchQueryRequest.java
 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Olingo4BatchQueryRequest.java
new file mode 100644
index 0000000..127a3ef
--- /dev/null
+++ 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Olingo4BatchQueryRequest.java
@@ -0,0 +1,76 @@
+/**
+ * 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.olingo4.api.batch;
+
+import java.util.Map;
+
+/**
+ * Batch Query part.
+ */
+public class Olingo4BatchQueryRequest extends Olingo4BatchRequest {
+
+    private Map<String, String> queryParams;
+
+    public Map<String, String> getQueryParams() {
+        return queryParams;
+    }
+
+    public static Olingo4BatchQueryRequestBuilder resourcePath(String 
resourcePath) {
+        if (resourcePath == null) {
+            throw new IllegalArgumentException("resourcePath");
+        }
+        return new 
Olingo4BatchQueryRequestBuilder().resourcePath(resourcePath);
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder("Batch Query Request{ 
").append(resourceUri).append("/").append(resourcePath).append(", 
headers=").append(headers).append(", queryParams=")
+            .append(queryParams).append('}').toString();
+    }
+
+    public static class Olingo4BatchQueryRequestBuilder {
+        private Olingo4BatchQueryRequest request = new 
Olingo4BatchQueryRequest();
+
+        public Olingo4BatchQueryRequest build() {
+            // avoid later NPEs
+            if (request.resourcePath == null) {
+                throw new IllegalArgumentException("Null resourcePath");
+            }
+            return request;
+        }
+
+        public Olingo4BatchQueryRequestBuilder resourceUri(String resourceUri) 
{
+            request.resourceUri = resourceUri;
+            return this;
+        }
+
+        public Olingo4BatchQueryRequestBuilder resourcePath(String 
resourcePath) {
+            request.resourcePath = resourcePath;
+            return this;
+        }
+
+        public Olingo4BatchQueryRequestBuilder headers(Map<String, String> 
headers) {
+            request.headers = headers;
+            return this;
+        }
+
+        public Olingo4BatchQueryRequestBuilder queryParams(Map<String, String> 
queryParams) {
+            request.queryParams = queryParams;
+            return this;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/camel/blob/e6eded4c/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Olingo4BatchRequest.java
----------------------------------------------------------------------
diff --git 
a/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Olingo4BatchRequest.java
 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Olingo4BatchRequest.java
new file mode 100644
index 0000000..8ad9568
--- /dev/null
+++ 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Olingo4BatchRequest.java
@@ -0,0 +1,44 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.olingo4.api.batch;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Base part in a multipart Batch request.
+ */
+public abstract class Olingo4BatchRequest {
+
+    protected String resourceUri;
+    protected String resourcePath;
+    protected Map<String, String> headers = new HashMap<String, String>();
+
+    public String getResourceUri() {
+        return resourceUri;
+    }
+    
+    public String getResourcePath() {
+        return resourcePath;
+    }
+
+    public Map<String, String> getHeaders() {
+        return headers;
+    }
+
+    public abstract String toString();
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/camel/blob/e6eded4c/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Olingo4BatchResponse.java
----------------------------------------------------------------------
diff --git 
a/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Olingo4BatchResponse.java
 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Olingo4BatchResponse.java
new file mode 100644
index 0000000..9d4ce91
--- /dev/null
+++ 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Olingo4BatchResponse.java
@@ -0,0 +1,63 @@
+/**
+ * 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.olingo4.api.batch;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Batch Response part.
+ */
+public class Olingo4BatchResponse {
+
+    private final int statusCode;
+    private final String statusInfo;
+
+    private final String contentId;
+
+    private final Map<String, String> headers;
+    private final Object body;
+
+    public Olingo4BatchResponse(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/e6eded4c/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Operation.java
----------------------------------------------------------------------
diff --git 
a/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Operation.java
 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Operation.java
new file mode 100644
index 0000000..5785cae
--- /dev/null
+++ 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/batch/Operation.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.olingo4.api.batch;
+
+/**
+* OData operation used by {@link 
org.apache.camel.component.olingo4.api.batch.Olingo4BatchChangeRequest}.
+*/
+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/e6eded4c/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/impl/AbstractFutureCallback.java
----------------------------------------------------------------------
diff --git 
a/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/impl/AbstractFutureCallback.java
 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/impl/AbstractFutureCallback.java
new file mode 100644
index 0000000..1a206fa
--- /dev/null
+++ 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/impl/AbstractFutureCallback.java
@@ -0,0 +1,111 @@
+/**
+ * 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.olingo4.api.impl;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.regex.Pattern;
+
+import org.apache.camel.component.olingo4.api.Olingo4ResponseHandler;
+import org.apache.commons.io.IOUtils;
+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.client.api.ODataClient;
+import org.apache.olingo.client.api.communication.ODataClientErrorException;
+import org.apache.olingo.client.api.serialization.ODataReader;
+import org.apache.olingo.client.core.ODataClientFactory;
+import org.apache.olingo.client.core.serialization.ODataReaderImpl;
+import org.apache.olingo.commons.api.ex.ODataError;
+import org.apache.olingo.commons.api.ex.ODataException;
+import org.apache.olingo.commons.api.format.ContentType;
+import org.apache.olingo.commons.api.http.HttpStatusCode;
+
+/**
+* Helper implementation of {@link org.apache.http.concurrent.FutureCallback}
+ * for {@link org.apache.camel.component.olingo4.api.impl.Olingo4AppImpl}
+*/
+public abstract class AbstractFutureCallback<T> implements 
FutureCallback<HttpResponse> {
+
+    public static final Pattern ODATA_MIME_TYPE_PATTERN = 
Pattern.compile("application/((atom)|(json)|(xml)).*");
+    public static final int NETWORK_CONNECT_TIMEOUT_ERROR = 599;
+    
+    private final Olingo4ResponseHandler<T> responseHandler;
+
+    AbstractFutureCallback(Olingo4ResponseHandler<T> responseHandler) {
+        this.responseHandler = responseHandler;
+    }
+
+    public static HttpStatusCode checkStatus(HttpResponse response) throws 
ODataException, ODataClientErrorException {
+        final StatusLine statusLine = response.getStatusLine();
+        HttpStatusCode httpStatusCode = 
HttpStatusCode.fromStatusCode(statusLine.getStatusCode());
+        if (HttpStatusCode.BAD_REQUEST.getStatusCode() <= 
httpStatusCode.getStatusCode() && httpStatusCode.getStatusCode() <= 
NETWORK_CONNECT_TIMEOUT_ERROR) {
+            if (response.getEntity() != null) {
+                try {
+                    final ContentType responseContentType = ContentType.parse(
+                        
response.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue());
+                              
+                    if 
(ODATA_MIME_TYPE_PATTERN.matcher(responseContentType.toContentTypeString()).matches())
 {
+                        final ODataReader reader = 
ODataClientFactory.getClient().getReader();
+                        final ODataError error = 
reader.readError(response.getEntity().getContent(), responseContentType);
+                        
+                        throw new ODataClientErrorException(statusLine, error);
+                    }
+                } catch (IOException e) {
+                    throw new ODataException(e.getMessage(), e);
+                }
+            }
+
+            throw new ODataException(statusLine.getReasonPhrase());
+        }
+
+        return httpStatusCode;
+    }
+
+    @Override
+    public final void completed(HttpResponse result) {
+        try {
+            // check response status
+            checkStatus(result);
+
+            onCompleted(result);
+        } catch (Exception e) {
+            failed(e);
+        } finally {
+            if (result instanceof Closeable) {
+                try {
+                    ((Closeable) result).close();
+                } catch (final IOException ignore) {
+                }
+            }
+        }
+    }
+
+    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/e6eded4c/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/impl/Olingo4AppImpl.java
----------------------------------------------------------------------
diff --git 
a/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/impl/Olingo4AppImpl.java
 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/impl/Olingo4AppImpl.java
new file mode 100644
index 0000000..7dffd1c
--- /dev/null
+++ 
b/components/camel-olingo4/camel-olingo4-api/src/main/java/org/apache/camel/component/olingo4/api/impl/Olingo4AppImpl.java
@@ -0,0 +1,841 @@
+/**
+ * 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.olingo4.api.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.camel.component.olingo4.api.Olingo4App;
+import org.apache.camel.component.olingo4.api.Olingo4ResponseHandler;
+import org.apache.camel.component.olingo4.api.batch.Olingo4BatchChangeRequest;
+import org.apache.camel.component.olingo4.api.batch.Olingo4BatchQueryRequest;
+import org.apache.camel.component.olingo4.api.batch.Olingo4BatchRequest;
+import org.apache.camel.component.olingo4.api.batch.Olingo4BatchResponse;
+import org.apache.camel.component.olingo4.api.batch.Operation;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.LineIterator;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.Consts;
+import org.apache.http.Header;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpResponseFactory;
+import org.apache.http.HttpVersion;
+import org.apache.http.StatusLine;
+import org.apache.http.client.entity.DecompressingEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+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.config.MessageConstraints;
+import org.apache.http.entity.AbstractHttpEntity;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.impl.DefaultHttpResponseFactory;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.conn.DefaultHttpResponseParser;
+import org.apache.http.impl.io.HttpTransportMetricsImpl;
+import org.apache.http.impl.io.SessionInputBufferImpl;
+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.BasicLineParser;
+import org.apache.olingo.client.api.ODataBatchConstants;
+import org.apache.olingo.client.api.ODataClient;
+import org.apache.olingo.client.api.communication.request.ODataStreamer;
+import 
org.apache.olingo.client.api.communication.request.batch.ODataBatchLineIterator;
+import org.apache.olingo.client.api.domain.ClientEntity;
+import org.apache.olingo.client.api.domain.ClientPrimitiveValue;
+import org.apache.olingo.client.api.domain.ClientProperty;
+import org.apache.olingo.client.api.serialization.ODataReader;
+import org.apache.olingo.client.api.serialization.ODataWriter;
+import org.apache.olingo.client.api.uri.SegmentType;
+import org.apache.olingo.client.core.ODataClientFactory;
+import 
org.apache.olingo.client.core.communication.request.batch.ODataBatchController;
+import 
org.apache.olingo.client.core.communication.request.batch.ODataBatchLineIteratorImpl;
+import 
org.apache.olingo.client.core.communication.request.batch.ODataBatchUtilities;
+import org.apache.olingo.client.core.http.HttpMerge;
+import org.apache.olingo.commons.api.Constants;
+import org.apache.olingo.commons.api.edm.Edm;
+import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
+import org.apache.olingo.commons.api.edm.EdmReturnType;
+import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
+import org.apache.olingo.commons.api.ex.ODataException;
+import org.apache.olingo.commons.api.format.ContentType;
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpStatusCode;
+import org.apache.olingo.server.api.OData;
+import org.apache.olingo.server.api.uri.UriInfo;
+import org.apache.olingo.server.api.uri.UriInfoKind;
+import org.apache.olingo.server.api.uri.UriParameter;
+import org.apache.olingo.server.api.uri.UriResource;
+import org.apache.olingo.server.api.uri.UriResourceEntitySet;
+import org.apache.olingo.server.api.uri.UriResourceKind;
+import org.apache.olingo.server.core.uri.UriResourceFunctionImpl;
+import org.apache.olingo.server.core.uri.parser.Parser;
+import org.apache.olingo.server.core.uri.parser.UriParserException;
+import org.apache.olingo.server.core.uri.validator.UriValidationException;
+
+/**
+ * Application API used by Olingo4 Component.
+ */
+public final class Olingo4AppImpl implements Olingo4App {
+
+    private static final String SEPARATOR = "/";
+    private static final String BOUNDARY_PREFIX = "batch_";
+    private static final String BOUNDARY_PARAMETER = "; boundary=";
+    private static final String BOUNDARY_DOUBLE_DASH = "--";
+    private static final String MULTIPART_MIME_TYPE = "multipart/";
+    private static final String CONTENT_ID_HEADER = "Content-ID";
+    private static final String CLIENT_ENTITY_FAKE_MARKER = "('X')";
+
+    private static final ContentType DEFAULT_CONTENT_TYPE = 
ContentType.create(ContentType.APPLICATION_JSON, ContentType.PARAMETER_CHARSET, 
Constants.UTF8);
+    private static final ContentType METADATA_CONTENT_TYPE = 
ContentType.create(ContentType.APPLICATION_XML, ContentType.PARAMETER_CHARSET, 
Constants.UTF8);
+    private static final ContentType TEXT_PLAIN_WITH_CS_UTF_8 = 
ContentType.create(ContentType.TEXT_PLAIN, ContentType.PARAMETER_CHARSET, 
Constants.UTF8);
+    private static final ContentType SERVICE_DOCUMENT_CONTENT_TYPE = 
ContentType.APPLICATION_JSON;
+    private static final ContentType BATCH_CONTENT_TYPE = 
ContentType.MULTIPART_MIXED;
+
+    /**
+     * Reference to CloseableHttpAsyncClient (default) or CloseableHttpClient
+     */
+    private final Closeable client;
+
+    /**
+     * Reference to ODataClient reader and writer
+     */
+    private final ODataClient odataClient = ODataClientFactory.getClient();
+    private final ODataReader odataReader = odataClient.getReader();
+    private final ODataWriter odataWriter = odataClient.getWriter();
+
+    private String serviceUri;
+    private ContentType contentType;
+    private Map<String, String> httpHeaders;
+
+    /**
+     * Create Olingo4 Application with default HTTP configuration.
+     */
+    public Olingo4AppImpl(String serviceUri) {
+        // By default create HTTP asynchronous client
+        this(serviceUri, (HttpClientBuilder)null);
+    }
+
+    /**
+     * Create Olingo4 Application with custom HTTP Asynchronous client builder.
+     *
+     * @param serviceUri Service Application base URI.
+     * @param builder custom HTTP client builder.
+     */
+    public Olingo4AppImpl(String serviceUri, HttpAsyncClientBuilder builder) {
+        setServiceUri(serviceUri);
+
+        CloseableHttpAsyncClient asyncClient;
+        if (builder == null) {
+            asyncClient = HttpAsyncClients.createDefault();
+        } else {
+            asyncClient = builder.build();
+        }
+        asyncClient.start();
+        this.client = asyncClient;
+        this.contentType = DEFAULT_CONTENT_TYPE;
+    }
+
+    /**
+     * Create Olingo4 Application with custom HTTP Synchronous client builder.
+     *
+     * @param serviceUri Service Application base URI.
+     * @param builder Custom HTTP Synchronous client builder.
+     */
+    public Olingo4AppImpl(String serviceUri, HttpClientBuilder builder) {
+        setServiceUri(serviceUri);
+
+        if (builder == null) {
+            this.client = HttpClients.createDefault();
+        } else {
+            this.client = builder.build();
+        }
+        this.contentType = DEFAULT_CONTENT_TYPE;
+    }
+
+    @Override
+    public void setServiceUri(String serviceUri) {
+        if (serviceUri == null || serviceUri.isEmpty()) {
+            throw new IllegalArgumentException("serviceUri");
+        }
+        this.serviceUri = serviceUri.endsWith(SEPARATOR) ? 
serviceUri.substring(0, serviceUri.length() - 1) : 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.toContentTypeString();
+    }
+
+    @Override
+    public void setContentType(String contentType) {
+        this.contentType = ContentType.parse(contentType);
+    }
+
+    @Override
+    public void close() {
+
+    }
+
+    @Override
+    public <T> void read(final Edm edm, final String resourcePath, final 
Map<String, String> queryParams, final Olingo4ResponseHandler<T> 
responseHandler) {
+        final String queryOptions = concatQueryParams(queryParams);
+        final UriInfo uriInfo = parseUri(edm, resourcePath, queryOptions);
+
+        execute(new HttpGet(createUri(resourcePath, queryOptions)), 
getResourceContentType(uriInfo), new AbstractFutureCallback<T>(responseHandler) 
{
+
+            @Override
+            public void onCompleted(HttpResponse result) throws IOException {
+                readContent(uriInfo, result.getEntity() != null ? 
result.getEntity().getContent() : null, responseHandler);
+            }
+
+        });
+    }
+
+    @Override
+    public void uread(final Edm edm, final String resourcePath, final 
Map<String, String> queryParams, final Olingo4ResponseHandler<InputStream> 
responseHandler) {
+        final String queryOptions = concatQueryParams(queryParams);
+        final UriInfo uriInfo = parseUri(edm, resourcePath, queryOptions);
+
+        execute(new HttpGet(createUri(resourcePath, queryOptions)), 
getResourceContentType(uriInfo), new 
AbstractFutureCallback<InputStream>(responseHandler) {
+
+            @Override
+            public void onCompleted(HttpResponse result) throws IOException {
+                InputStream responseStream = result.getEntity() != null ? 
result.getEntity().getContent() : null;
+                if (responseStream != null && result.getEntity() instanceof 
DecompressingEntity) {
+                    // In case of GZIP compression it's necessary to create
+                    // InputStream from the source byte array
+                    responseHandler.onResponse(new 
ByteArrayInputStream(IOUtils.toByteArray(responseStream)));
+                } else {
+                    responseHandler.onResponse(responseStream);
+                }
+            }
+        });
+    }
+
+    @Override
+    public <T> void create(Edm edm, String resourcePath, Object data, 
Olingo4ResponseHandler<T> responseHandler) {
+        final UriInfo 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, 
Olingo4ResponseHandler<T> responseHandler) {
+        final UriInfo uriInfo = parseUri(edm, resourcePath, null);
+
+        writeContent(edm, new HttpPut(createUri(resourcePath, null)), uriInfo, 
data, responseHandler);
+    }
+
+    @Override
+    public void delete(String resourcePath, final 
Olingo4ResponseHandler<HttpStatusCode> responseHandler) {
+        execute(new HttpDelete(createUri(resourcePath)), contentType, new 
AbstractFutureCallback<HttpStatusCode>(responseHandler) {
+            @Override
+            public void onCompleted(HttpResponse result) {
+                final StatusLine statusLine = result.getStatusLine();
+                
responseHandler.onResponse(HttpStatusCode.fromStatusCode(statusLine.getStatusCode()));
+            }
+        });
+    }
+
+    @Override
+    public <T> void patch(Edm edm, String resourcePath, Object data, 
Olingo4ResponseHandler<T> responseHandler) {
+        final UriInfo 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, 
Olingo4ResponseHandler<T> responseHandler) {
+        final UriInfo uriInfo = parseUri(edm, resourcePath, null);
+
+        writeContent(edm, new HttpMerge(createUri(resourcePath, null)), 
uriInfo, data, responseHandler);
+    }
+
+    @Override
+    public void batch(Edm edm, Object data, 
Olingo4ResponseHandler<List<Olingo4BatchResponse>> responseHandler) {
+        final UriInfo uriInfo = parseUri(edm, SegmentType.BATCH.getValue(), 
null);
+
+        writeContent(edm, new HttpPost(createUri(SegmentType.BATCH.getValue(), 
null)), uriInfo, data, responseHandler);
+    }
+
+    private ContentType getResourceContentType(UriInfo uriInfo) {
+        ContentType resourceContentType;
+        switch (uriInfo.getKind()) {
+        case service:
+            // service document
+            resourceContentType = SERVICE_DOCUMENT_CONTENT_TYPE;
+            break;
+        case metadata:
+            // metadata
+            resourceContentType = METADATA_CONTENT_TYPE;
+            break;
+        case resource:
+            List<UriResource> listResource = uriInfo.getUriResourceParts();
+            UriResourceKind lastResourceKind = 
listResource.get(listResource.size() - 1).getKind();
+            // is it a $value or $count URI??
+            if (lastResourceKind == UriResourceKind.count || lastResourceKind 
== UriResourceKind.value) {
+                resourceContentType = TEXT_PLAIN_WITH_CS_UTF_8;
+            } else {
+                resourceContentType = contentType;
+            }
+            break;
+        default:
+            resourceContentType = contentType;
+        }
+        return resourceContentType;
+    }
+
+    private <T> void readContent(UriInfo uriInfo, InputStream content, 
Olingo4ResponseHandler<T> responseHandler) {
+        try {
+            responseHandler.onResponse(this.<T> readContent(uriInfo, content));
+        } catch (ODataException e) {
+            responseHandler.onException(e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> T readContent(UriInfo uriInfo, InputStream content) throws 
ODataException {
+        T response = null;
+        switch (uriInfo.getKind()) {
+        case service:
+            // service document
+            response = (T)odataReader.readServiceDocument(content, 
SERVICE_DOCUMENT_CONTENT_TYPE);
+            break;
+
+        case metadata:
+            // $metadata
+            response = (T)odataReader.readMetadata(content);
+            break;
+        case resource:
+            // any resource entity
+            List<UriResource> listResource = uriInfo.getUriResourceParts();
+            UriResourceKind lastResourceKind = 
listResource.get(listResource.size() - 1).getKind();
+            switch (lastResourceKind) {
+            case entitySet:
+                UriResourceEntitySet uriResourceEntitySet = 
(UriResourceEntitySet)listResource.get(listResource.size() - 1);
+                List<UriParameter> keyPredicates = 
uriResourceEntitySet.getKeyPredicates();
+                // Check result type: single Entity or EntitySet based
+                // on key predicate detection
+                if (keyPredicates.size() == 1) {
+                    response = (T)odataReader.readEntity(content, 
getResourceContentType(uriInfo));
+                } else {
+                    response = (T)odataReader.readEntitySet(content, 
getResourceContentType(uriInfo));
+                }
+                break;
+            case count:
+                String stringCount = null;
+                try {
+                    stringCount = IOUtils.toString(content, Consts.UTF_8);
+                    response = (T)Long.valueOf(stringCount);
+                } catch (IOException e) {
+                    throw new ODataException("Error during $count value 
deserialization", e);
+                } catch (NumberFormatException e) {
+                    throw new ODataException("Error during $count value 
conversion: " + stringCount, e);
+                }
+                break;
+            case value:
+                try {
+                    ClientPrimitiveValue value = 
odataClient.getObjectFactory().newPrimitiveValueBuilder().setType(EdmPrimitiveTypeKind.String)
+                        .setValue(IOUtils.toString(content, 
Consts.UTF_8)).build();
+                    response = (T)value;
+                } catch (IOException e) {
+                    throw new ODataException("Error during $value 
deserialization", e);
+                }
+                break;
+            case primitiveProperty:
+            case complexProperty:
+                ClientProperty property = odataReader.readProperty(content, 
getResourceContentType(uriInfo));
+                if (property.hasPrimitiveValue()) {
+                    response = (T)property.getPrimitiveValue();
+                } else if (property.hasComplexValue()) {
+                    response = (T)property.getComplexValue();
+                } else {
+                    throw new ODataException("Unsupported property: " + 
property.getName());
+                }
+                break;
+            case function:
+                UriResourceFunctionImpl uriResourceFunction = 
(UriResourceFunctionImpl)listResource.get(listResource.size() - 1);
+                EdmReturnType functionReturnType = 
uriResourceFunction.getFunction().getReturnType();
+
+                switch (functionReturnType.getType().getKind()) {
+                case ENTITY:
+                    if (functionReturnType.isCollection()) {
+                        response = (T)odataReader.readEntitySet(content, 
getResourceContentType(uriInfo));
+                    } else {
+                        response = (T)odataReader.readEntity(content, 
getResourceContentType(uriInfo));
+                    }
+                    break;
+                case PRIMITIVE:
+                case COMPLEX:
+                    ClientProperty functionProperty = 
odataReader.readProperty(content, getResourceContentType(uriInfo));
+                    if (functionProperty.hasPrimitiveValue()) {
+                        response = (T)functionProperty.getPrimitiveValue();
+                    } else if (functionProperty.hasComplexValue()) {
+                        response = (T)functionProperty.getComplexValue();
+                    } else {
+                        throw new ODataException("Unsupported property: " + 
functionProperty.getName());
+                    }
+                    break;
+                default:
+                    throw new ODataException("Unsupported function return type 
" + uriInfo.getKind().name());
+                }
+                break;
+            default:
+                throw new ODataException("Unsupported resource type: " + 
lastResourceKind.name());
+            }
+            break;
+
+        default:
+            throw new ODataException("Unsupported resource type " + 
uriInfo.getKind().name());
+        }
+
+        return response;
+    }
+
+    private <T> void writeContent(final Edm edm, 
HttpEntityEnclosingRequestBase httpEntityRequest, final UriInfo uriInfo, final 
Object content,
+                                  final Olingo4ResponseHandler<T> 
responseHandler) {
+
+        try {
+            httpEntityRequest.setEntity(writeContent(edm, uriInfo, content));
+
+            final Header requestContentTypeHeader = 
httpEntityRequest.getEntity().getContentType();
+            final ContentType requestContentType = requestContentTypeHeader != 
null ? ContentType.parse(requestContentTypeHeader.getValue()) : contentType;
+
+            execute(httpEntityRequest, requestContentType, new 
AbstractFutureCallback<T>(responseHandler) {
+                @SuppressWarnings("unchecked")
+                @Override
+                public void onCompleted(HttpResponse result) throws 
IOException, ODataException {
+
+                    // if a entity is created (via POST request) the response
+                    // body contains the new created entity
+                    HttpStatusCode statusCode = 
HttpStatusCode.fromStatusCode(result.getStatusLine().getStatusCode());
+
+                    // look for no content, or no response body!!!
+                    final boolean noEntity = result.getEntity() == null || 
result.getEntity().getContentLength() == 0;
+                    if (statusCode == HttpStatusCode.NO_CONTENT || noEntity) {
+                        
responseHandler.onResponse((T)HttpStatusCode.fromStatusCode(result.getStatusLine().getStatusCode()));
+                    } else {
+                        if (uriInfo.getKind() == UriInfoKind.resource) {
+                            List<UriResource> listResource = 
uriInfo.getUriResourceParts();
+                            UriResourceKind lastResourceKind = 
listResource.get(listResource.size() - 1).getKind();
+                            switch (lastResourceKind) {
+                            case entitySet:
+                                if (content instanceof ClientEntity) {
+                                    ClientEntity entity = 
odataReader.readEntity(result.getEntity().getContent(),
+                                                                               
  ContentType.parse(result.getEntity().getContentType().getValue()));
+                                    responseHandler.onResponse((T)entity);
+                                } else {
+                                    throw new ODataException("Unsupported 
content type: " + content);
+                                }
+                                break;
+                            default:
+                                break;
+                            }
+                        } else if (uriInfo.getKind() == UriInfoKind.batch) {
+                            List<Olingo4BatchResponse> batchResponse = 
parseBatchResponse(edm, result, (List<Olingo4BatchRequest>)content);
+                            responseHandler.onResponse((T)batchResponse);
+                        } else {
+                            throw new ODataException("Unsupported resource 
type: " + uriInfo.getKind().name());
+                        }
+                    }
+                }
+            });
+
+        } catch (ODataException e) {
+            responseHandler.onException(e);
+        }
+    }
+
+    private AbstractHttpEntity writeContent(final Edm edm, final UriInfo 
uriInfo, final Object content) throws ODataException {
+        InputStream requestStream = null;
+        AbstractHttpEntity httpEntity = null;
+        if (uriInfo.getKind() == UriInfoKind.resource) {
+            // any resource entity
+            List<UriResource> listResource = uriInfo.getUriResourceParts();
+            UriResourceKind lastResourceKind = 
listResource.get(listResource.size() - 1).getKind();
+            switch (lastResourceKind) {
+            case entitySet:
+                if (content instanceof ClientEntity) {
+                    requestStream = 
odataWriter.writeEntity((ClientEntity)content, getResourceContentType(uriInfo));
+                } else {
+                    throw new ODataException("Unsupported content type: " + 
content);
+                }
+                break;
+            default:
+                throw new ODataException("Unsupported resource type: " + 
lastResourceKind);
+            }
+            try {
+                httpEntity = new 
ByteArrayEntity(IOUtils.toByteArray(requestStream));
+            } catch (IOException e) {
+                throw new ODataException("Error during converting input stream 
to byte array", e);
+            }
+            httpEntity.setChunked(false);
+
+        } else if (uriInfo.getKind() == UriInfoKind.batch) {
+            final String boundary = BOUNDARY_PREFIX + UUID.randomUUID();
+            final String contentHeader = BATCH_CONTENT_TYPE + 
BOUNDARY_PARAMETER + boundary;
+            final List<Olingo4BatchRequest> batchParts = 
(List<Olingo4BatchRequest>)content;
+
+            requestStream = serializeBatchRequest(edm, batchParts, 
BOUNDARY_DOUBLE_DASH + boundary);
+            try {
+                httpEntity = new 
ByteArrayEntity(IOUtils.toByteArray(requestStream));
+            } catch (IOException e) {
+                throw new ODataException("Error during converting input stream 
to byte array", e);
+            }
+            httpEntity.setChunked(false);
+            httpEntity.setContentType(contentHeader);
+        } else {
+            throw new ODataException("Unsupported resource type: " + 
uriInfo.getKind().name());
+        }
+
+        return httpEntity;
+    }
+
+    private InputStream serializeBatchRequest(final Edm edm, final 
List<Olingo4BatchRequest> batchParts, String boundary) throws ODataException {
+        final ByteArrayOutputStream batchRequestHeaderOutputStream = new 
ByteArrayOutputStream();
+
+        try {
+            
batchRequestHeaderOutputStream.write(boundary.getBytes(Constants.UTF8));
+            batchRequestHeaderOutputStream.write(ODataStreamer.CRLF);
+
+            for (Olingo4BatchRequest batchPart : batchParts) {
+                writeHttpHeader(batchRequestHeaderOutputStream, 
HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_HTTP.toContentTypeString());
+                writeHttpHeader(batchRequestHeaderOutputStream, 
ODataBatchConstants.ITEM_TRANSFER_ENCODING_LINE, null);
+
+                if (batchPart instanceof Olingo4BatchQueryRequest) {
+                    final Olingo4BatchQueryRequest batchQueryPart = 
(Olingo4BatchQueryRequest)batchPart;
+                    final String batchQueryUri = 
createUri(StringUtils.isBlank(batchQueryPart.getResourceUri()) ? serviceUri : 
batchQueryPart.getResourceUri(),
+                                                           
batchQueryPart.getResourcePath(), 
concatQueryParams(batchQueryPart.getQueryParams()));
+                    final UriInfo uriInfo = parseUri(edm, 
batchQueryPart.getResourcePath(), 
concatQueryParams(batchQueryPart.getQueryParams()));
+                    batchRequestHeaderOutputStream.write(ODataStreamer.CRLF);
+
+                    batchRequestHeaderOutputStream.write((HttpGet.METHOD_NAME 
+ " " + batchQueryUri + " " + HttpVersion.HTTP_1_1).getBytes(Constants.UTF8));
+                    batchRequestHeaderOutputStream.write(ODataStreamer.CRLF);
+                    writeHttpHeader(batchRequestHeaderOutputStream, 
HttpHeaders.ACCEPT, getResourceContentType(uriInfo).toContentTypeString());
+
+                    batchRequestHeaderOutputStream.write(ODataStreamer.CRLF);
+                    
batchRequestHeaderOutputStream.write(boundary.getBytes(Constants.UTF8));
+                    batchRequestHeaderOutputStream.write(ODataStreamer.CRLF);
+                } else if (batchPart instanceof Olingo4BatchChangeRequest) {
+                    final Olingo4BatchChangeRequest batchChangePart = 
(Olingo4BatchChangeRequest)batchPart;
+                    final String batchChangeUri = 
createUri(StringUtils.isBlank(batchChangePart.getResourceUri()) ? serviceUri : 
batchChangePart.getResourceUri(),
+                                                            
batchChangePart.getResourcePath(), null);
+                    final UriInfo uriInfo = parseUri(edm, 
batchChangePart.getResourcePath(), null);
+
+                    if (batchChangePart.getOperation() != Operation.DELETE) {
+                        writeHttpHeader(batchRequestHeaderOutputStream, 
CONTENT_ID_HEADER, batchChangePart.getContentId());
+                    }
+
+                    batchRequestHeaderOutputStream.write(ODataStreamer.CRLF);
+                    batchRequestHeaderOutputStream
+                        .write((batchChangePart.getOperation().getHttpMethod() 
+ " " + batchChangeUri + " " + HttpVersion.HTTP_1_1).getBytes(Constants.UTF8));
+                    batchRequestHeaderOutputStream.write(ODataStreamer.CRLF);
+                    writeHttpHeader(batchRequestHeaderOutputStream, 
HttpHeader.ODATA_VERSION, ODataServiceVersion.V40.toString());
+                    writeHttpHeader(batchRequestHeaderOutputStream, 
HttpHeaders.ACCEPT, getResourceContentType(uriInfo).toContentTypeString());
+                    writeHttpHeader(batchRequestHeaderOutputStream, 
HttpHeaders.CONTENT_TYPE, 
getResourceContentType(uriInfo).toContentTypeString());
+
+                    if (batchChangePart.getOperation() != Operation.DELETE) {
+                        
batchRequestHeaderOutputStream.write(ODataStreamer.CRLF);
+                        AbstractHttpEntity httpEnity = writeContent(edm, 
uriInfo, batchChangePart.getBody());
+
+                        
batchRequestHeaderOutputStream.write(IOUtils.toByteArray(httpEnity.getContent()));
+                        
batchRequestHeaderOutputStream.write(ODataStreamer.CRLF);
+                        
batchRequestHeaderOutputStream.write(ODataStreamer.CRLF);
+                    } else {
+                        
batchRequestHeaderOutputStream.write(ODataStreamer.CRLF);
+                    }
+
+                    
batchRequestHeaderOutputStream.write(boundary.getBytes(Constants.UTF8));
+                    batchRequestHeaderOutputStream.write(ODataStreamer.CRLF);
+                } else {
+                    throw new ODataException("Unsupported batch part request 
object type: " + batchPart);
+                }
+            }
+        } catch (Exception e) {
+            throw new ODataException("Error during batch request 
serialization", e);
+        }
+        return new 
ByteArrayInputStream(batchRequestHeaderOutputStream.toByteArray());
+    }
+
+    private void writeHttpHeader(ByteArrayOutputStream headerOutputStream, 
String headerName, String headerValue) throws IOException {
+        headerOutputStream.write(createHttpHeader(headerName, 
headerValue).getBytes(Constants.UTF8));
+        headerOutputStream.write(ODataStreamer.CRLF);
+    }
+
+    private String createHttpHeader(String headerName, String headerValue) {
+        return headerName + (StringUtils.isBlank(headerValue) ? "" : (": " + 
headerValue));
+    }
+
+    private List<Olingo4BatchResponse> parseBatchResponse(final Edm edm, 
HttpResponse response, List<Olingo4BatchRequest> batchRequest) throws 
ODataException {
+        List<Olingo4BatchResponse> batchResponse = new <Olingo4BatchResponse> 
ArrayList();
+        try {
+            final Header[] contentHeaders = 
response.getHeaders(HttpHeader.CONTENT_TYPE);
+            final ODataBatchLineIterator batchLineIterator = new 
ODataBatchLineIteratorImpl(IOUtils.lineIterator(response.getEntity().getContent(),
 Constants.UTF8));
+            final String batchBoundary = 
ODataBatchUtilities.getBoundaryFromHeader(getHeadersCollection(contentHeaders));
+            final ODataBatchController batchController = new 
ODataBatchController(batchLineIterator, batchBoundary);
+
+            batchController.getBatchLineIterator().next();
+            int batchRequestIndex = 0;
+            while (batchController.getBatchLineIterator().hasNext()) {
+                OutputStream os = new ByteArrayOutputStream();
+                ODataBatchUtilities.readBatchPart(batchController, os, false);
+                Object content = null;
+                final Olingo4BatchRequest batchPartRequest = 
(Olingo4BatchRequest)batchRequest.get(batchRequestIndex);
+                final HttpResponse batchPartHttpResponse = 
constructBatchPartHttpResponse(new 
ByteArrayInputStream(((ByteArrayOutputStream)os).toByteArray()));
+                final StatusLine batchPartStatusLine = 
batchPartHttpResponse.getStatusLine();
+                final int batchPartLineStatusCode = 
batchPartStatusLine.getStatusCode();
+                Map<String, String> batchPartHeaders = 
getHeadersValueMap(batchPartHttpResponse.getAllHeaders());
+                if (batchPartRequest instanceof Olingo4BatchQueryRequest) {
+                    Olingo4BatchQueryRequest batchPartQueryRequest = 
(Olingo4BatchQueryRequest)batchPartRequest;
+                    final UriInfo uriInfo = parseUri(edm, 
batchPartQueryRequest.getResourcePath(), null);
+
+                    if (HttpStatusCode.BAD_REQUEST.getStatusCode() <= 
batchPartLineStatusCode && batchPartLineStatusCode <= 
AbstractFutureCallback.NETWORK_CONNECT_TIMEOUT_ERROR) {
+                        final ContentType responseContentType = 
ContentType.parse(batchPartHttpResponse.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue());
+                        content = 
odataReader.readError(batchPartHttpResponse.getEntity().getContent(), 
responseContentType);
+                    } else if (batchPartLineStatusCode == 
HttpStatusCode.NO_CONTENT.getStatusCode()) {
+                        // nothing to do if NO_CONTENT returning
+                    } else {
+                        content = readContent(uriInfo, 
batchPartHttpResponse.getEntity().getContent());
+                    }
+
+                    Olingo4BatchResponse batchPartResponse = new 
Olingo4BatchResponse(batchPartStatusLine.getStatusCode(), 
batchPartStatusLine.getReasonPhrase(), null,
+                                                                               
       batchPartHeaders, content);
+                    batchResponse.add(batchPartResponse);
+                } else if (batchPartRequest instanceof 
Olingo4BatchChangeRequest) {
+                    Olingo4BatchChangeRequest batchPartChangeRequest = 
(Olingo4BatchChangeRequest)batchPartRequest;
+
+                    if (batchPartLineStatusCode != 
HttpStatusCode.NO_CONTENT.getStatusCode()) {
+                        if (HttpStatusCode.BAD_REQUEST.getStatusCode() <= 
batchPartLineStatusCode
+                            && batchPartLineStatusCode <= 
AbstractFutureCallback.NETWORK_CONNECT_TIMEOUT_ERROR) {
+                            final ContentType responseContentType = 
ContentType.parse(batchPartHttpResponse.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue());
+                            content = 
odataReader.readError(response.getEntity().getContent(), responseContentType);
+                        } else {
+                            final UriInfo uriInfo = parseUri(edm, 
batchPartChangeRequest.getResourcePath()
+                                                                  + 
(batchPartChangeRequest.getOperation() == Operation.CREATE ? 
CLIENT_ENTITY_FAKE_MARKER : ""),
+                                                             null);
+                            content = readContent(uriInfo, 
batchPartHttpResponse.getEntity().getContent());
+                        }
+                    }
+                    Olingo4BatchResponse batchPartResponse = new 
Olingo4BatchResponse(batchPartStatusLine.getStatusCode(), 
batchPartStatusLine.getReasonPhrase(),
+                                                                               
       batchPartChangeRequest.getContentId(), batchPartHeaders, content);
+                    batchResponse.add(batchPartResponse);
+                } else {
+                    throw new ODataException("Unsupported batch part request 
object type: " + batchPartRequest);
+                }
+                batchRequestIndex++;
+            }
+
+        } catch (IOException | HttpException e) {
+            throw new ODataException(e);
+        }
+        return batchResponse;
+    }
+
+    private HttpResponse constructBatchPartHttpResponse(InputStream 
batchPartStream) throws IOException, HttpException {
+        final LineIterator lines = IOUtils.lineIterator(batchPartStream, 
Constants.UTF8);
+        final ByteArrayOutputStream headerOutputStream = new 
ByteArrayOutputStream();
+        final ByteArrayOutputStream bodyOutputStream = new 
ByteArrayOutputStream();
+
+        boolean startBatchPartHeader = false;
+        boolean startBatchPartBody = false;
+
+        // Iterate through lines in the batch part
+        while (lines.hasNext()) {
+            String line = lines.nextLine().trim();
+            // Ignore all lines below HTTP/1.1 line
+            if (line.startsWith(HttpVersion.HTTP)) {
+                // This is the first header line
+                startBatchPartHeader = true;
+            }
+            // Body starts with empty string after header lines
+            if (startBatchPartHeader && StringUtils.isBlank(line)) {
+                startBatchPartHeader = false;
+                startBatchPartBody = true;
+            }
+            if (startBatchPartHeader) {
+                // Write header to the output stream
+                headerOutputStream.write(line.getBytes(Constants.UTF8));
+                headerOutputStream.write(ODataStreamer.CRLF);
+            } else if (startBatchPartBody && StringUtils.isNotBlank(line)) {
+                // Write body to the output stream
+                bodyOutputStream.write(line.getBytes(Constants.UTF8));
+                bodyOutputStream.write(ODataStreamer.CRLF);
+            }
+        }
+
+        // Prepare for parsing headers in to the HttpResponse object
+        HttpTransportMetricsImpl metrics = new HttpTransportMetricsImpl();
+        SessionInputBufferImpl sessionInputBuffer = new 
SessionInputBufferImpl(metrics, 2048);
+        HttpResponseFactory responseFactory = new DefaultHttpResponseFactory();
+
+        sessionInputBuffer.bind(new 
ByteArrayInputStream(headerOutputStream.toByteArray()));
+        DefaultHttpResponseParser responseParser = new 
DefaultHttpResponseParser(sessionInputBuffer, new BasicLineParser(), 
responseFactory, MessageConstraints.DEFAULT);
+
+        // Parse HTTP response and headers
+        HttpResponse response = responseParser.parse();
+        // Set body inside entity
+        response.setEntity(new 
ByteArrayEntity(bodyOutputStream.toByteArray()));
+
+        return response;
+    }
+
+    private Collection<String> getHeadersCollection(Header[] headers) {
+        Collection<String> headersCollection = new ArrayList();
+        for (Header header : Arrays.asList(headers)) {
+            headersCollection.add(header.getValue());
+        }
+        return headersCollection;
+    }
+
+    private Map<String, String> getHeadersValueMap(Header[] headers) {
+        Map<String, String> headersValueMap = new HashMap();
+        for (Header header : Arrays.asList(headers)) {
+            headersValueMap.put(header.getName(), header.getValue());
+        }
+        return headersValueMap;
+    }
+
+    private String createUri(String resourcePath) {
+        return createUri(serviceUri, resourcePath, null);
+    }
+
+    private String createUri(String resourcePath, String queryOptions) {
+        return createUri(serviceUri, resourcePath, queryOptions);
+    }
+
+    private String createUri(String resourceUri, String resourcePath, String 
queryOptions) {
+
+        final StringBuilder absolutUri = new 
StringBuilder(resourceUri).append(SEPARATOR).append(resourcePath);
+        if (queryOptions != null && !queryOptions.isEmpty()) {
+            absolutUri.append("/?" + queryOptions);
+        }
+        return absolutUri.toString();
+
+    }
+
+    private String concatQueryParams(final Map<String, String> queryParams) {
+        final StringBuilder concatQuery = new StringBuilder("");
+        if (queryParams != null && !queryParams.isEmpty()) {
+            int nParams = queryParams.size();
+            int index = 0;
+            for (Map.Entry<String, String> entry : queryParams.entrySet()) {
+                
concatQuery.append(entry.getKey()).append('=').append(entry.getValue());
+                if (++index < nParams) {
+                    concatQuery.append('&');
+                }
+            }
+        }
+        return concatQuery.toString().replaceAll("  *", "%20");
+    }
+
+    private static UriInfo parseUri(Edm edm, String resourcePath, String 
queryOptions) {
+        Parser parser = new Parser(edm, OData.newInstance());
+        UriInfo result;
+
+        try {
+            result = parser.parseUri(resourcePath, queryOptions, null);
+        } catch (UriParserException | UriValidationException e) {
+            throw new IllegalArgumentException("parseUri (" + resourcePath + 
"," + queryOptions + "): " + e.getMessage(), e);
+        }
+        return result;
+    }
+
+    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 (!ContentType.APPLICATION_FORM_URLENCODED.equals(contentType) && 
!contentType.toContentTypeString().startsWith(MULTIPART_MIME_TYPE)) {
+            // otherwise accept what is being sent
+            httpUriRequest.addHeader(HttpHeaders.ACCEPT, contentTypeString);
+        }
+
+        // is something being sent?
+        if (httpUriRequest instanceof HttpEntityEnclosingRequestBase && 
httpUriRequest.getFirstHeader(HttpHeaders.CONTENT_TYPE) == null) {
+            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 'Accept-Charset' header to avoid BOM marker presents inside
+        // response stream
+        if (!httpUriRequest.containsHeader(HttpHeaders.ACCEPT_CHARSET)) {
+            httpUriRequest.addHeader(HttpHeaders.ACCEPT_CHARSET, 
Constants.UTF8);
+        }
+
+        // add client protocol version if not specified
+        if (!httpUriRequest.containsHeader(HttpHeader.ODATA_VERSION)) {
+            httpUriRequest.addHeader(HttpHeader.ODATA_VERSION, 
ODataServiceVersion.V40.toString());
+        }
+        if (!httpUriRequest.containsHeader(HttpHeader.ODATA_MAX_VERSION)) {
+            httpUriRequest.addHeader(HttpHeader.ODATA_MAX_VERSION, 
ODataServiceVersion.V40.toString());
+        }
+
+        // execute request
+        if (client instanceof CloseableHttpAsyncClient) {
+            ((CloseableHttpAsyncClient)client).execute(httpUriRequest, 
callback);
+        } else {
+            // invoke the callback methods explicitly after executing the
+            // request synchronously
+            try {
+                CloseableHttpResponse result = 
((CloseableHttpClient)client).execute(httpUriRequest);
+                callback.completed(result);
+            } catch (IOException e) {
+                callback.failed(e);
+            }
+        }
+    }
+}

Reply via email to