You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by fm...@apache.org on 2014/03/13 16:23:46 UTC

[4/7] OLINGO-205 ODataJClient request/response layer implementation imported

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/ODataRequestImpl.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/ODataRequestImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/ODataRequestImpl.java
new file mode 100644
index 0000000..b4ae74e
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/ODataRequestImpl.java
@@ -0,0 +1,483 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.client.core.communication.request;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.net.URI;
+import java.util.Collection;
+import java.util.Collections;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.impl.client.DecompressingHttpClient;
+import org.apache.olingo.client.api.ODataClient;
+import org.apache.olingo.client.api.V3Configuration;
+import org.apache.olingo.client.api.communication.ODataClientErrorException;
+import org.apache.olingo.client.api.communication.ODataServerErrorException;
+import org.apache.olingo.client.api.communication.header.HeaderName;
+import org.apache.olingo.client.api.communication.header.ODataHeaderValues;
+import org.apache.olingo.client.api.communication.header.ODataHeaders;
+import org.apache.olingo.client.api.communication.request.ODataRequest;
+import org.apache.olingo.client.api.communication.request.ODataStreamer;
+import org.apache.olingo.client.api.communication.response.ODataResponse;
+import org.apache.olingo.client.api.format.ODataMediaFormat;
+import org.apache.olingo.client.api.format.ODataPubFormat;
+import org.apache.olingo.client.api.format.ODataValueFormat;
+import org.apache.olingo.client.api.http.HttpClientException;
+import org.apache.olingo.client.api.http.HttpMethod;
+import org.apache.olingo.client.core.data.JSONErrorImpl;
+import org.apache.olingo.client.core.data.XMLErrorImpl;
+import org.apache.olingo.client.api.data.Error;
+import org.apache.olingo.client.core.communication.header.ODataHeadersImpl;
+import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract representation of an OData request. Get instance by using factories.
+ *
+ * @param <T> Accepted content-type formats by the request in object.
+ *
+ * @see CUDRequestFactory
+ * @see BatchRequestFactory
+ * @see InvokeRequestFactory
+ * @see StreamedRequestFactory
+ */
+public class ODataRequestImpl<T extends Enum<T>> implements ODataRequest {
+
+  /**
+   * Logger.
+   */
+  protected static final Logger LOG = LoggerFactory.getLogger(ODataRequest.class);
+
+  protected final ODataClient odataClient;
+
+  protected final Class<T> formatRef;
+
+  /**
+   * OData request method.
+   */
+  protected final HttpMethod method;
+
+  /**
+   * OData request header.
+   */
+  protected final ODataHeadersImpl odataHeaders;
+
+  /**
+   * Target URI.
+   */
+  protected final URI uri;
+
+  /**
+   * HTTP client.
+   */
+  protected final HttpClient httpClient;
+
+  /**
+   * HTTP request.
+   */
+  protected final HttpUriRequest request;
+
+  /**
+   * Constructor.
+   *
+   * @param odataClient client instance getting this request
+   * @param formatRef reference class for the format being used
+   * @param method HTTP request method. If configured X-HTTP-METHOD header will be used.
+   * @param uri OData request URI.
+   */
+  protected ODataRequestImpl(final ODataClient odataClient,
+          final Class<T> formatRef, final HttpMethod method, final URI uri) {
+
+    this.odataClient = odataClient;
+
+    this.formatRef = formatRef;
+    this.method = method;
+
+    // initialize default headers
+    this.odataHeaders = (ODataHeadersImpl) odataClient.getVersionHeaders();
+
+    // target uri
+    this.uri = uri;
+
+    HttpClient _httpClient = odataClient.getConfiguration().getHttpClientFactory().
+            createHttpClient(this.method, this.uri);
+    if (odataClient.getConfiguration().isGzipCompression()) {
+      _httpClient = new DecompressingHttpClient(_httpClient);
+    }
+    this.httpClient = _httpClient;
+
+    this.request = odataClient.getConfiguration().getHttpUriRequestFactory().
+            createHttpUriRequest(this.method, this.uri);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @SuppressWarnings("unchecked")
+  public T getDefaultFormat() {
+    return (T) (formatRef.equals(ODataPubFormat.class)
+            ? odataClient.getConfiguration().getDefaultPubFormat()
+            : (formatRef.equals(ODataValueFormat.class)
+            ? odataClient.getConfiguration().getDefaultValueFormat()
+            : (formatRef.equals(ODataMediaFormat.class)
+            ? odataClient.getConfiguration().getDefaultMediaFormat()
+            : odataClient.getConfiguration().getDefaultFormat())));
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public URI getURI() {
+    return uri;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Collection<String> getHeaderNames() {
+    return odataHeaders.getHeaderNames();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getHeader(final String name) {
+    return odataHeaders.getHeader(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ODataRequest setAccept(final String value) {
+    odataHeaders.setHeader(HeaderName.accept, value);
+    return this;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ODataRequest setIfMatch(final String value) {
+    odataHeaders.setHeader(HeaderName.ifMatch, value);
+    return this;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ODataRequest setIfNoneMatch(final String value) {
+    odataHeaders.setHeader(HeaderName.ifNoneMatch, value);
+    return this;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ODataRequest setPrefer(final String value) {
+    odataHeaders.setHeader(HeaderName.prefer, value);
+    return this;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ODataRequest setXHTTPMethod(final String value) {
+    odataHeaders.setHeader(HeaderName.xHttpMethod, value);
+    return this;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ODataRequest setContentType(final String value) {
+    odataHeaders.setHeader(HeaderName.contentType, value);
+    return this;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ODataRequest setSlug(final String value) {
+    odataHeaders.setHeader(HeaderName.slug, value);
+    return this;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ODataRequest addCustomHeader(final String name, final String value) {
+    odataHeaders.setHeader(name, value);
+    return this;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getAccept() {
+    final String acceptHead = odataHeaders.getHeader(HeaderName.accept);
+    return StringUtils.isBlank(acceptHead) ? getDefaultFormat().toString() : acceptHead;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getIfMatch() {
+    return odataHeaders.getHeader(HeaderName.ifMatch);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getIfNoneMatch() {
+    return odataHeaders.getHeader(HeaderName.ifNoneMatch);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getPrefer() {
+    return odataHeaders.getHeader(HeaderName.prefer);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String getContentType() {
+    final String contentTypeHead = odataHeaders.getHeader(HeaderName.contentType);
+    return StringUtils.isBlank(contentTypeHead) ? getDefaultFormat().toString() : contentTypeHead;
+  }
+
+  /**
+   * ${@inheritDoc }
+   */
+  @Override
+  public HttpMethod getMethod() {
+    return method;
+  }
+
+  /**
+   * Gets request headers.
+   *
+   * @return request headers.
+   */
+  public ODataHeaders getHeader() {
+    return odataHeaders;
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public byte[] toByteArray() {
+    final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    try {
+      final StringBuilder requestBuilder = new StringBuilder();
+      requestBuilder.append(getMethod().toString()).append(" ").
+              append(uri.toString()).append(" ").append("HTTP/1.1");
+
+      baos.write(requestBuilder.toString().getBytes());
+
+      baos.write(ODataStreamer.CRLF);
+
+      // Set Content-Type and Accept headers with default values, if not yet set
+      if (StringUtils.isBlank(odataHeaders.getHeader(HeaderName.contentType))) {
+        setContentType(getContentType());
+      }
+      if (StringUtils.isBlank(odataHeaders.getHeader(HeaderName.accept))) {
+        setAccept(getAccept());
+      }
+
+      for (String name : getHeaderNames()) {
+        final String value = getHeader(name);
+
+        if (StringUtils.isNotBlank(value)) {
+          baos.write((name + ": " + value).getBytes());
+          baos.write(ODataStreamer.CRLF);
+        }
+      }
+
+      return baos.toByteArray();
+    } catch (IOException e) {
+      throw new IllegalStateException(e);
+    } finally {
+      IOUtils.closeQuietly(baos);
+    }
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public InputStream rawExecute() {
+    try {
+      final HttpEntity httpEntity = doExecute().getEntity();
+      return httpEntity == null ? null : httpEntity.getContent();
+    } catch (IOException e) {
+      throw new HttpClientException(e);
+    } catch (RuntimeException e) {
+      this.request.abort();
+      throw new HttpClientException(e);
+    }
+  }
+
+  /**
+   * Builds the request and execute it.
+   *
+   * @return HttpReponse object.
+   */
+  protected HttpResponse doExecute() {
+    // Set Content-Type and Accept headers with default values, if not yet set
+    if (StringUtils.isBlank(odataHeaders.getHeader(HeaderName.contentType))) {
+      setContentType(getContentType());
+    }
+    if (StringUtils.isBlank(odataHeaders.getHeader(HeaderName.accept))) {
+      setAccept(getAccept());
+    }
+
+    // Add header for KeyAsSegment management
+    if (odataClient.getServiceVersion() == ODataServiceVersion.V30
+            && ((V3Configuration) odataClient.getConfiguration()).isKeyAsSegment()) {
+      addCustomHeader(
+              HeaderName.dataServiceUrlConventions.toString(), ODataHeaderValues.keyAsSegment);
+    }
+
+    // Add all available headers
+    for (String key : getHeaderNames()) {
+      this.request.addHeader(key, odataHeaders.getHeader(key));
+    }
+
+    if (LOG.isDebugEnabled()) {
+      for (Header header : this.request.getAllHeaders()) {
+        LOG.debug("HTTP header being sent: " + header);
+      }
+    }
+
+    final HttpResponse response;
+    try {
+      response = this.httpClient.execute(this.request);
+    } catch (IOException e) {
+      throw new HttpClientException(e);
+    } catch (RuntimeException e) {
+      this.request.abort();
+      throw new HttpClientException(e);
+    }
+
+    if (response.getStatusLine().getStatusCode() >= 500) {
+      throw new ODataServerErrorException(response.getStatusLine());
+    } else if (response.getStatusLine().getStatusCode() >= 400) {
+      try {
+        final HttpEntity httpEntity = response.getEntity();
+        if (httpEntity == null) {
+          throw new ODataClientErrorException(response.getStatusLine());
+        } else {
+          final boolean isXML = getAccept().indexOf("json") == -1;
+          Error error;
+
+          try {
+            error = odataClient.getReader().readError(httpEntity.getContent(), isXML);
+          } catch (IllegalArgumentException e) {
+            LOG.warn("Error deserializing error response", e);
+            error = getGenericError(
+                    response.getStatusLine().getStatusCode(),
+                    response.getStatusLine().getReasonPhrase(),
+                    isXML);
+          }
+
+          throw new ODataClientErrorException(response.getStatusLine(), error);
+        }
+      } catch (IOException e) {
+        throw new HttpClientException(
+                "Received '" + response.getStatusLine() + "' but could not extract error body", e);
+      }
+    }
+
+    return response;
+  }
+
+  /**
+   * Gets an empty response that can be initialized by a stream.
+   * <p>
+   * This method has to be used to build response items about a batch request.
+   *
+   * @param <V> ODataResppnse type.
+   * @return empty OData response instance.
+   */
+  @SuppressWarnings("unchecked")
+  public <V extends ODataResponse> V getResponseTemplate() {
+
+    for (Class<?> clazz : this.getClass().getDeclaredClasses()) {
+      if (ODataResponse.class.isAssignableFrom(clazz)) {
+        try {
+          final Constructor<?> constructor = clazz.getDeclaredConstructor(this.getClass());
+          constructor.setAccessible(true);
+          return (V) constructor.newInstance(this);
+        } catch (Exception e) {
+          LOG.error("Error retrieving response class template instance", e);
+        }
+      }
+    }
+
+    throw new IllegalStateException("No response class template has been found");
+  }
+
+  private Error getGenericError(final int code, final String errorMsg, final boolean isXML) {
+    final Error error;
+    if (isXML) {
+      error = new XMLErrorImpl();
+      final XMLErrorImpl.Message msg = new XMLErrorImpl.Message(
+              Collections.singletonMap("", (Object) errorMsg));
+
+      ((XMLErrorImpl) error).setMessage(msg);
+      ((XMLErrorImpl) error).setCode(String.valueOf(code));
+    } else {
+      error = new JSONErrorImpl();
+      final JSONErrorImpl.Message msg = new JSONErrorImpl.Message();
+      msg.setValue(errorMsg);
+
+      ((JSONErrorImpl) error).setMessage(msg);
+      ((JSONErrorImpl) error).setCode(String.valueOf(code));
+    }
+
+    return error;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractBatchRequestFactory.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractBatchRequestFactory.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractBatchRequestFactory.java
new file mode 100644
index 0000000..c92a243
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractBatchRequestFactory.java
@@ -0,0 +1,36 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
+ * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.olingo.client.core.communication.request.batch;
+
+import org.apache.olingo.client.api.ODataClient;
+import org.apache.olingo.client.api.communication.request.batch.BatchRequestFactory;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchRequest;
+
+/**
+ * OData batch request factory class.
+ */
+public abstract class AbstractBatchRequestFactory implements BatchRequestFactory {
+
+  private static final long serialVersionUID = -3875283254713404483L;
+
+  protected final ODataClient client;
+
+  protected AbstractBatchRequestFactory(final ODataClient client) {
+    this.client = client;
+  }
+
+  @Override
+  public ODataBatchRequest getBatchRequest(final String serviceRoot) {
+    return new ODataBatchRequestImpl(client, client.getURIBuilder(serviceRoot).appendBatchSegment().build());
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchRequestItem.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchRequestItem.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchRequestItem.java
new file mode 100644
index 0000000..e85ce36
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchRequestItem.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.client.core.communication.request.batch;
+
+import org.apache.olingo.client.api.ODataBatchConstants;
+import org.apache.olingo.client.api.communication.request.ODataBatchableRequest;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchRequest;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchRequestItem;
+import org.apache.olingo.client.core.communication.request.AbstractODataStreamer;
+
+/**
+ * Abstract representation of a batch request item.
+ */
+public abstract class AbstractODataBatchRequestItem extends AbstractODataStreamer
+        implements ODataBatchRequestItem {
+
+  /**
+   * Stream started check.
+   */
+  protected boolean hasStreamedSomething = false;
+
+  /**
+   * Stream open check.
+   */
+  private boolean open = false;
+
+  /**
+   * OData batch request.
+   */
+  protected ODataBatchRequest req;
+
+  /**
+   * Constructor.
+   *
+   * @param req OData batch request.
+   */
+  public AbstractODataBatchRequestItem(final ODataBatchRequest req) {
+    super(req.getOutputStream());
+    this.open = true;
+    this.req = req;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean isOpen() {
+    return open;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void close() {
+    closeItem();
+    open = false;
+  }
+
+  /**
+   * Stream the given request header.
+   * <p>
+   * Use this method to stream changeset items.
+   *
+   * @param request request to be batched.
+   * @param contentId changeset item id.
+   */
+  protected void streamRequestHeader(final ODataBatchableRequest request, final int contentId) {
+    //stream batch content type
+    stream(ODataBatchConstants.ITEM_CONTENT_TYPE_LINE.getBytes());
+    newLine();
+    stream(ODataBatchConstants.ITEM_TRANSFER_ENCODING_LINE.getBytes());
+    newLine();
+    stream((ODataBatchConstants.CHANGESET_CONTENT_ID_NAME + ":" + contentId).getBytes());
+    newLine();
+    newLine();
+  }
+
+  /**
+   * Stream the given request header.
+   *
+   * @param request request to be batched.
+   */
+  protected void streamRequestHeader(final ODataBatchableRequest request) {
+    //stream batch content type
+    stream(ODataBatchConstants.ITEM_CONTENT_TYPE_LINE.getBytes());
+    newLine();
+    stream(ODataBatchConstants.ITEM_TRANSFER_ENCODING_LINE.getBytes());
+    newLine();
+    newLine();
+
+    stream(request.toByteArray());
+    newLine();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean hasStreamedSomething() {
+    return hasStreamedSomething;
+  }
+
+  /**
+   * Closes the current item.
+   */
+  protected abstract void closeItem();
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchResponseItem.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchResponseItem.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchResponseItem.java
new file mode 100644
index 0000000..b0a9ce9
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/AbstractODataBatchResponseItem.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.client.core.communication.request.batch;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchLineIterator;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchResponseItem;
+import org.apache.olingo.client.api.communication.response.ODataResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract representation of a response item about a batch request.
+ */
+public abstract class AbstractODataBatchResponseItem implements ODataBatchResponseItem {
+
+  /**
+   * Logger.
+   */
+  protected static final Logger LOG = LoggerFactory.getLogger(ODataBatchResponseItem.class);
+
+  /**
+   * Expected OData responses for the current batch response item.
+   */
+  protected final Map<String, ODataResponse> responses = new HashMap<String, ODataResponse>();
+
+  /**
+   * Expected OData responses iterator.
+   */
+  protected Iterator<ODataResponse> expectedItemsIterator;
+
+  /**
+   * Changeset controller. Gives more information about the type of batch item.
+   */
+  private final boolean changeset;
+
+  /**
+   * Batch response line iterator.
+   */
+  protected ODataBatchLineIterator batchLineIterator;
+
+  /**
+   * Batch boundary.
+   */
+  protected String boundary;
+
+  /**
+   * Gives information about the batch response item status.
+   */
+  protected boolean closed = false;
+
+  /**
+   * Constructor.
+   *
+   * @param isChangeset 'TRUE' if the current batch response item is a changeset.
+   */
+  public AbstractODataBatchResponseItem(boolean isChangeset) {
+    this.changeset = isChangeset;
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public void addResponse(final String contentId, final ODataResponse res) {
+    if (closed) {
+      throw new IllegalStateException("Invalid batch item because explicitely closed");
+    }
+    responses.put(contentId, res);
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public void initFromBatch(final ODataBatchLineIterator batchLineIterator, final String boundary) {
+    if (closed) {
+      throw new IllegalStateException("Invalid batch item because explicitely closed");
+    }
+    LOG.debug("Init from batch - boundary '{}'", boundary);
+    this.batchLineIterator = batchLineIterator;
+    this.boundary = boundary;
+  }
+
+  /**
+   * Gets response about the given contentId.
+   *
+   * @param contentId response identifier (a specific contentId in case of changeset item).
+   * @return ODataResponse corresponding to the given contentId.
+   */
+  protected ODataResponse getResponse(final String contentId) {
+    if (closed) {
+      throw new IllegalStateException("Invalid batch item because explicitely closed");
+    }
+    return responses.get(contentId);
+  }
+
+  /**
+   * Gets OData responses iterator.
+   *
+   * @return OData responses iterator.
+   */
+  protected Iterator<ODataResponse> getResponseIterator() {
+    if (closed) {
+      throw new IllegalStateException("Invalid batch item because explicitely closed");
+    }
+    return responses.values().iterator();
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public final boolean isChangeset() {
+    return changeset;
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public void close() {
+    for (ODataResponse response : responses.values()) {
+      response.close();
+    }
+    closed = true;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchController.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchController.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchController.java
new file mode 100644
index 0000000..c2377d8
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchController.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.client.core.communication.request.batch;
+
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchLineIterator;
+
+/**
+ * Utility class used to communicate batch info.
+ */
+public class ODataBatchController {
+
+  /**
+   * Batch validity.
+   */
+  private boolean validBatch = true;
+
+  /**
+   * Batch boundary.
+   */
+  private final String boundary;
+
+  /**
+   * Batch line iterator.
+   */
+  private final ODataBatchLineIterator batchLineIterator;
+
+  /**
+   * Constructor.
+   *
+   * @param batchLineIterator batch line iterator.
+   * @param boundary batch boundary.
+   */
+  public ODataBatchController(final ODataBatchLineIterator batchLineIterator, final String boundary) {
+    this.batchLineIterator = batchLineIterator;
+    this.boundary = boundary;
+  }
+
+  /**
+   * Checks if batch is valid.
+   *
+   * @return batch validity.
+   */
+  public boolean isValidBatch() {
+    return validBatch;
+  }
+
+  /**
+   * Sets batch validity.
+   *
+   * @param validBatch validity.
+   */
+  public void setValidBatch(final boolean validBatch) {
+    this.validBatch = validBatch;
+  }
+
+  /**
+   * Gest batch boundary.
+   *
+   * @return batch boundary.
+   */
+  public String getBoundary() {
+    return boundary;
+  }
+
+  /**
+   * Gest batch line iterator.
+   *
+   * @return batch line iterator.
+   */
+  public ODataBatchLineIterator getBatchLineIterator() {
+    return batchLineIterator;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchLineIteratorImpl.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchLineIteratorImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchLineIteratorImpl.java
new file mode 100644
index 0000000..34d621a
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchLineIteratorImpl.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.client.core.communication.request.batch;
+
+import org.apache.commons.io.LineIterator;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchLineIterator;
+
+/**
+ * Batch line iterator class.
+ */
+public class ODataBatchLineIteratorImpl implements ODataBatchLineIterator {
+
+  /**
+   * Stream line iterator.
+   */
+  private final LineIterator batchLineIterator;
+
+  /**
+   * Last cached line.
+   */
+  private String current;
+
+  /**
+   * Constructor.
+   *
+   * @param batchLineIterator stream line iterator.
+   */
+  public ODataBatchLineIteratorImpl(final LineIterator batchLineIterator) {
+    this.batchLineIterator = batchLineIterator;
+    this.current = null;
+  }
+
+  /**
+   * Checks if batch has next line.
+   *
+   * @return 'TRUE' if has next line; 'FALSE' otherwise.
+   */
+  @Override
+  public boolean hasNext() {
+    return batchLineIterator.hasNext();
+  }
+
+  /**
+   * Gets next line.
+   *
+   * @return next line.
+   */
+  @Override
+  public String next() {
+    return nextLine();
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public String nextLine() {
+    current = batchLineIterator.nextLine();
+    return current;
+  }
+
+  /**
+   * Unsupported operation.
+   */
+  @Override
+  public void remove() {
+    throw new UnsupportedOperationException("Unsupported operation");
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public String getCurrent() {
+    return current;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchRequestImpl.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchRequestImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchRequestImpl.java
new file mode 100644
index 0000000..054f467
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchRequestImpl.java
@@ -0,0 +1,255 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.client.core.communication.request.batch;
+
+import java.io.IOException;
+import java.io.PipedOutputStream;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.olingo.client.api.ODataBatchConstants;
+import org.apache.olingo.client.api.ODataClient;
+import org.apache.olingo.client.api.communication.request.batch.BatchStreamManager;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchRequest;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchRequestItem;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchResponseItem;
+import org.apache.olingo.client.api.communication.request.batch.ODataChangeset;
+import org.apache.olingo.client.api.communication.request.batch.ODataRetrieve;
+import org.apache.olingo.client.api.communication.response.ODataBatchResponse;
+import org.apache.olingo.client.api.http.HttpMethod;
+import org.apache.olingo.client.core.communication.request.AbstractODataStreamManager;
+import org.apache.olingo.client.core.communication.request.streamed.AbstractODataStreamedRequest;
+import org.apache.olingo.client.core.communication.response.ODataResponseImpl;
+import org.apache.olingo.client.core.communication.response.batch.ODataBatchResponseManager;
+
+/**
+ * This class implements a batch request.
+ */
+public class ODataBatchRequestImpl extends AbstractODataStreamedRequest<ODataBatchResponse, BatchStreamManager>
+        implements ODataBatchRequest {
+
+  /**
+   * Batch request boundary.
+   */
+  private final String boundary;
+
+  /**
+   * Expected batch response items.
+   */
+  private final List<ODataBatchResponseItem> expectedResItems = new ArrayList<ODataBatchResponseItem>();
+
+  /**
+   * Constructor.
+   *
+   * @param odataClient client instance getting this request
+   * @param uri batch request URI (http://serviceRoot/$batch)
+   */
+  ODataBatchRequestImpl(final ODataClient odataClient, final URI uri) {
+    super(odataClient, HttpMethod.POST, uri);
+
+    // create a random UUID value for boundary
+    boundary = "batch_" + UUID.randomUUID().toString();
+
+    // specify the contentType header
+    setContentType(ODataBatchConstants.MULTIPART_CONTENT_TYPE + ";" + ODataBatchConstants.BOUNDARY + "=" + boundary);
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  protected BatchStreamManager getStreamManager() {
+    if (streamManager == null) {
+      streamManager = new BatchStreamManagerImpl(this);
+    }
+    return (BatchStreamManager) streamManager;
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public PipedOutputStream getOutputStream() {
+    return getStreamManager().getBodyStreamWriter();
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public ODataBatchRequestImpl rawAppend(final byte[] toBeStreamed) throws IOException {
+    getStreamManager().getBodyStreamWriter().write(toBeStreamed);
+    return this;
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public ODataBatchRequestImpl rawAppend(final byte[] toBeStreamed, int off, int len) throws IOException {
+    getStreamManager().getBodyStreamWriter().write(toBeStreamed, off, len);
+    return this;
+  }
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * This operation is unsupported by a batch request.
+   */
+  @Override
+  public void batch(ODataBatchRequest req) {
+    throw new UnsupportedOperationException("A batch request is not batchable");
+  }
+
+  /**
+   * This class implements a response to a batch request.
+   *
+   * @see com.msopentech.odatajclient.engine.communication.request.ODataBatchRequest
+   */
+  private class ODataBatchResponseImpl extends ODataResponseImpl implements ODataBatchResponse {
+
+    /**
+     * Constructor.
+     *
+     * @param client HTTP client.
+     * @param res HTTP response.
+     */
+    private ODataBatchResponseImpl(final HttpClient client, final HttpResponse res) {
+      super(client, res);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Iterator<ODataBatchResponseItem> getBody() {
+      return new ODataBatchResponseManager(this, expectedResItems);
+    }
+  }
+
+  /**
+   * Batch request payload management.
+   */
+  public class BatchStreamManagerImpl extends AbstractODataStreamManager<ODataBatchResponse> {
+
+    /**
+     * Batch request current item.
+     */
+    private ODataBatchRequestItem currentItem = null;
+
+    /**
+     * batch request reference.
+     */
+    private final ODataBatchRequest req;
+
+    /**
+     * Private constructor.
+     *
+     * @param req batch request reference.
+     */
+    private BatchStreamManagerImpl(final ODataBatchRequest req) {
+      super(ODataBatchRequestImpl.this.futureWrapper);
+      this.req = req;
+    }
+
+    /**
+     * Gets a changeset batch item instance. A changeset can be submitted embedded into a batch request only.
+     *
+     * @return ODataChangeset instance.
+     */
+    public ODataChangeset addChangeset() {
+      closeCurrentItem();
+
+      // stream dash boundary
+      streamDashBoundary();
+
+      final ODataChangesetResponseItem expectedResItem = new ODataChangesetResponseItem();
+      expectedResItems.add(expectedResItem);
+
+      currentItem = new ODataChangesetImpl(req, expectedResItem);
+
+      return (ODataChangeset) currentItem;
+    }
+
+    /**
+     * Gets a retrieve batch item instance. A retrieve item can be submitted embedded into a batch request only.
+     *
+     * @return ODataRetrieve instance.
+     */
+    public ODataRetrieve addRetrieve() {
+      closeCurrentItem();
+
+      // stream dash boundary
+      streamDashBoundary();
+
+      final ODataRetrieveResponseItem expectedResItem = new ODataRetrieveResponseItem();
+      currentItem = new ODataRetrieveImpl(req, expectedResItem);
+
+      expectedResItems.add(expectedResItem);
+
+      return (ODataRetrieve) currentItem;
+    }
+
+    /**
+     * Close the current streamed item.
+     */
+    private void closeCurrentItem() {
+      if (currentItem != null) {
+        currentItem.close();
+      }
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    protected ODataBatchResponse getResponse(final long timeout, final TimeUnit unit) {
+      closeCurrentItem();
+      streamCloseDelimiter();
+      finalizeBody();
+      return new ODataBatchResponseImpl(httpClient, getHttpResponse(timeout, unit));
+    }
+
+    /**
+     * Streams dash boundary.
+     */
+    private void streamDashBoundary() {
+      // preamble
+      newLine();
+
+      // stream batch-boundary
+      stream(("--" + boundary).getBytes());
+      newLine();
+    }
+
+    /**
+     * Streams close delimiter.
+     */
+    private void streamCloseDelimiter() {
+      // stream close-delimiter
+      newLine();
+      stream(("--" + boundary + "--").getBytes());
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchUtilities.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchUtilities.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchUtilities.java
new file mode 100644
index 0000000..a33f7cb
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataBatchUtilities.java
@@ -0,0 +1,329 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.client.core.communication.request.batch;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.AbstractMap;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.LineIterator;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.olingo.client.api.Constants;
+import org.apache.olingo.client.api.ODataBatchConstants;
+import org.apache.olingo.client.api.communication.header.HeaderName;
+import org.apache.olingo.client.api.communication.request.ODataStreamer;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchLineIterator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility class for batch requests and responses.
+ */
+public class ODataBatchUtilities {
+
+  public static enum BatchItemType {
+
+    NONE,
+    CHANGESET,
+    RETRIEVE
+
+  }
+  /**
+   * Logger.
+   */
+  private static final Logger LOG = LoggerFactory.getLogger(ODataBatchUtilities.class);
+
+  /**
+   * Response line syntax.
+   */
+  private static final Pattern RESPONSE_PATTERN =
+          Pattern.compile("HTTP/\\d\\.\\d (\\d+) (.*)", Pattern.CASE_INSENSITIVE);
+
+  /**
+   * Reads batch part taking source and delimiter (boundary) from given batch controller.
+   * <p>
+   * Usually used to consume/discard useless lines.
+   *
+   * @param batchController batch controller.
+   * @param checkCurrent if 'TRUE' the current line will be included into the delimiter verification.
+   * @return latest read line.
+   */
+  public static String readBatchPart(final ODataBatchController batchController, final boolean checkCurrent) {
+    return readBatchPart(batchController, null, -1, checkCurrent);
+  }
+
+  /**
+   * Reads the given number of line from the given batch wrapped into the batch controller.
+   * <p>
+   * Usually used to consume/discard useless lines.
+   *
+   * @param batchController batch controller.
+   * @param count number of batch line to be read.
+   * @return latest read line.
+   */
+  public static String readBatchPart(final ODataBatchController batchController, final int count) {
+    return readBatchPart(batchController, null, count, true);
+  }
+
+  /**
+   * Reads batch part taking source and delimiter (boundary) from given batch controller.
+   * <p>
+   * Usually used to read an entire batch part.
+   *
+   * @param batchController batch controller.
+   * @param os destination stream of batch part (null to discard).
+   * @param checkCurrent if 'TRUE' the current line will be included into the delimiter verification.
+   * @return latest read line.
+   */
+  public static String readBatchPart(
+          final ODataBatchController controller, final OutputStream os, final boolean checkCurrent) {
+
+    return readBatchPart(controller, os, -1, checkCurrent);
+  }
+
+  /**
+   * Reads batch part taking source and delimiter (boundary) from given batch controller.
+   * <p>
+   * Usually used to read an entire batch part.
+   *
+   * @param batchController batch controller.
+   * @param os destination stream of batch part (null to discard).
+   * @param count number of batch line to be read.
+   * @param checkCurrent if 'TRUE' the current line will be included into the delimiter verification.
+   * @return latest read line.
+   */
+  public static String readBatchPart(
+          final ODataBatchController controller, final OutputStream os, final int count, final boolean checkCurrent) {
+
+    String currentLine;
+
+    synchronized (controller.getBatchLineIterator()) {
+      currentLine = checkCurrent ? controller.getBatchLineIterator().getCurrent() : null;
+
+      if (count < 0) {
+        try {
+
+          boolean notEndLine = isNotEndLine(controller, currentLine);
+
+          while (controller.isValidBatch() && notEndLine && controller.getBatchLineIterator().hasNext()) {
+
+            currentLine = controller.getBatchLineIterator().nextLine();
+            LOG.debug("Read line '{}' (end-line '{}')", currentLine, controller.getBoundary());
+
+            notEndLine = isNotEndLine(controller, currentLine);
+
+            if (notEndLine && os != null) {
+              os.write(currentLine.getBytes(Constants.UTF8));
+              os.write(ODataStreamer.CRLF);
+            }
+          }
+
+        } catch (IOException e) {
+          LOG.error("Error reading batch part", e);
+          throw new IllegalStateException(e);
+        }
+
+      } else {
+        for (int i = 0;
+                controller.isValidBatch() && controller.getBatchLineIterator().hasNext() && i < count; i++) {
+          currentLine = controller.getBatchLineIterator().nextLine();
+        }
+      }
+    }
+
+    return currentLine;
+  }
+
+  /**
+   * Reads headers from the batch starting from the given position.
+   *
+   * @param iterator batch iterator.
+   * @return Map of header name in header values.
+   */
+  public static Map<String, Collection<String>> readHeaders(final ODataBatchLineIterator iterator) {
+    final Map<String, Collection<String>> target =
+            new TreeMap<String, Collection<String>>(String.CASE_INSENSITIVE_ORDER);
+
+    readHeaders(iterator, target);
+    return target;
+  }
+
+  /**
+   * Reads headers from the batch starting from the given position.
+   * <p>
+   * Retrieved headers will be added to the map given by target parameter.
+   *
+   * @param iterator batch iterator.
+   * @param target destination of the retrieved headers.
+   */
+  public static void readHeaders(
+          final ODataBatchLineIterator iterator, final Map<String, Collection<String>> target) {
+
+    try {
+      final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      readBatchPart(new ODataBatchController(iterator, null), baos, true);
+
+      final LineIterator headers = IOUtils.lineIterator(new ByteArrayInputStream(baos.toByteArray()), Constants.UTF8);
+      while (headers.hasNext()) {
+        final String line = headers.nextLine().trim();
+        if (StringUtils.isNotBlank(line)) {
+          addHeaderLine(line, target);
+        }
+      }
+    } catch (Exception e) {
+      LOG.error("Error retrieving headers", e);
+      throw new IllegalStateException(e);
+    }
+  }
+
+  /**
+   * Parses and adds the given header line to the given target map.
+   *
+   * @param headerLine header line to be added.
+   * @param targetMap target map.
+   */
+  public static void addHeaderLine(final String headerLine, final Map<String, Collection<String>> targetMap) {
+    final int sep = headerLine.indexOf(':');
+    if (sep > 0 && sep < headerLine.length() - 1) {
+      final String key = headerLine.substring(0, sep).trim();
+      final Collection<String> value;
+      if (targetMap.containsKey(key)) {
+        value = targetMap.get(key);
+      } else {
+        value = new HashSet<String>();
+        targetMap.put(key, value);
+      }
+      value.add(headerLine.substring(sep + 1, headerLine.length()).trim());
+    }
+  }
+
+  /**
+   * Retrieved batch boundary from the given content-type header values.
+   *
+   * @param contentType content-types.
+   * @return batch boundary.
+   */
+  public static String getBoundaryFromHeader(final Collection<String> contentType) {
+    final String boundaryKey = ODataBatchConstants.BOUNDARY + "=";
+
+    if (contentType == null || contentType.isEmpty() || !contentType.toString().contains(boundaryKey)) {
+      throw new IllegalArgumentException("Invalid content type");
+    }
+
+    final String headerValue = contentType.toString();
+
+    final int start = headerValue.indexOf(boundaryKey) + boundaryKey.length();
+    int end = headerValue.indexOf(';', start);
+
+    if (end < 0) {
+      end = headerValue.indexOf(']', start);
+    }
+
+    final String res = headerValue.substring(start, end);
+    return res.startsWith("--") ? res : "--" + res;
+  }
+
+  /**
+   * Retrieves response line from the given position.
+   *
+   * @param iterator batch iterator.
+   * @return retrieved response line.
+   */
+  public static Map.Entry<Integer, String> readResponseLine(final ODataBatchLineIterator iterator) {
+    final String line = readBatchPart(new ODataBatchController(iterator, null), 1);
+    LOG.debug("Response line '{}'", line);
+
+    final Matcher matcher = RESPONSE_PATTERN.matcher(line.trim());
+
+    if (matcher.matches()) {
+      return new AbstractMap.SimpleEntry<Integer, String>(Integer.valueOf(matcher.group(1)), matcher.group(2));
+    }
+
+    throw new IllegalArgumentException("Invalid response line '" + line + "'");
+  }
+
+  /**
+   * Retrieves headers of the next batch item.
+   *
+   * @param iterator batch line iterator.
+   * @param boundary batch boundary.
+   * @return batch item headers.
+   */
+  public static Map<String, Collection<String>> nextItemHeaders(
+          final ODataBatchLineIterator iterator, final String boundary) {
+
+    final Map<String, Collection<String>> headers =
+            new TreeMap<String, Collection<String>>(String.CASE_INSENSITIVE_ORDER);
+
+    final String line = ODataBatchUtilities.readBatchPart(new ODataBatchController(iterator, boundary), true);
+
+    if (line != null && line.trim().equals(boundary)) {
+      ODataBatchUtilities.readHeaders(iterator, headers);
+    }
+
+    LOG.debug("Retrieved batch item headers {}", headers);
+    return headers;
+  }
+
+  /**
+   * Retrieves item type from item headers.
+   *
+   * @param headers batch item headers.
+   * @return batch item type.
+   */
+  public static BatchItemType getItemType(final Map<String, Collection<String>> headers) {
+
+    final BatchItemType nextItemType;
+
+    final String contentType = headers.containsKey(HeaderName.contentType.toString())
+            ? headers.get(HeaderName.contentType.toString()).toString() : StringUtils.EMPTY;
+
+    if (contentType.contains(ODataBatchConstants.MULTIPART_CONTENT_TYPE)) {
+      nextItemType = BatchItemType.CHANGESET;
+    } else if (contentType.contains(ODataBatchConstants.ITEM_CONTENT_TYPE)) {
+      nextItemType = BatchItemType.RETRIEVE;
+    } else {
+      nextItemType = BatchItemType.NONE;
+    }
+
+    LOG.debug("Retrieved next item type {}", nextItemType);
+    return nextItemType;
+  }
+
+  /**
+   * Checks if the given line is the expected end-line.
+   *
+   * @param controller batch controller.
+   * @param line line to be checked.
+   * @return 'TRUE' if the line is not the end-line; 'FALSE' otherwise.
+   */
+  private static boolean isNotEndLine(final ODataBatchController controller, final String line) {
+    return line == null
+            || (StringUtils.isBlank(controller.getBoundary()) && StringUtils.isNotBlank(line))
+            || (StringUtils.isNotBlank(controller.getBoundary()) && !line.startsWith(controller.getBoundary()));
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetImpl.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetImpl.java
new file mode 100644
index 0000000..4bb486e
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetImpl.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.client.core.communication.request.batch;
+
+import java.util.UUID;
+import org.apache.olingo.client.api.ODataBatchConstants;
+import org.apache.olingo.client.api.communication.header.HeaderName;
+import org.apache.olingo.client.api.communication.request.ODataBatchableRequest;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchRequest;
+import org.apache.olingo.client.api.communication.request.batch.ODataChangeset;
+import org.apache.olingo.client.api.http.HttpMethod;
+import org.apache.olingo.client.core.communication.request.ODataRequestImpl;
+
+/**
+ * Changeset wrapper for the corresponding batch item.
+ */
+public class ODataChangesetImpl extends AbstractODataBatchRequestItem
+        implements ODataChangeset {
+
+  /**
+   * ContentId.
+   */
+  private int contentId = 0;
+
+  /**
+   * Changeset boundary.
+   */
+  private final String boundary;
+
+  /**
+   * Expected changeset response items.
+   */
+  private final ODataChangesetResponseItem expectedResItem;
+
+  /**
+   * Constructor.
+   *
+   * @param req batch request.
+   * @param expectedResItem expected OData response items.
+   */
+  ODataChangesetImpl(final ODataBatchRequest req, final ODataChangesetResponseItem expectedResItem) {
+    super(req);
+    this.expectedResItem = expectedResItem;
+
+    // create a random UUID value for boundary
+    boundary = "changeset_" + UUID.randomUUID().toString();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public int getLastContentId() {
+    return contentId;
+  }
+
+  /**
+   * Close changeset item an send changeset request footer.
+   */
+  @Override
+  protected void closeItem() {
+    // stream close-delimiter
+    if (hasStreamedSomething) {
+      newLine();
+      stream(("--" + boundary + "--").getBytes());
+      newLine();
+      newLine();
+    }
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public ODataChangeset addRequest(final ODataBatchableRequest request) {
+    if (!isOpen()) {
+      throw new IllegalStateException("Current batch item is closed");
+    }
+
+    if (request.getMethod() == HttpMethod.GET) {
+      throw new IllegalArgumentException("Invalid request. GET method not allowed in changeset");
+    }
+
+    if (!hasStreamedSomething) {
+      stream((HeaderName.contentType.toString() + ": "
+              + ODataBatchConstants.MULTIPART_CONTENT_TYPE + ";boundary=" + boundary).getBytes());
+
+      newLine();
+      newLine();
+
+      hasStreamedSomething = true;
+    }
+
+    contentId++;
+
+    // preamble
+    newLine();
+
+    // stream batch-boundary
+    stream(("--" + boundary).getBytes());
+    newLine();
+
+    // stream the request
+    streamRequestHeader(request, contentId);
+
+    request.batch(req, String.valueOf(contentId));
+
+    // add request to the list
+    expectedResItem.addResponse(String.valueOf(contentId), ((ODataRequestImpl) request).getResponseTemplate());
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetResponseItem.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetResponseItem.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetResponseItem.java
new file mode 100644
index 0000000..4a077b5
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataChangesetResponseItem.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.client.core.communication.request.batch;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import org.apache.olingo.client.api.ODataBatchConstants;
+import org.apache.olingo.client.api.communication.response.ODataResponse;
+
+/**
+ * Changeset wrapper for the corresponding batch item.
+ */
+public class ODataChangesetResponseItem extends AbstractODataBatchResponseItem {
+
+  /**
+   * Last cached OData response.
+   */
+  private ODataResponse current = null;
+
+  /**
+   * Constructor.
+   */
+  public ODataChangesetResponseItem() {
+    super(true);
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public boolean hasNext() {
+    if (closed) {
+      throw new IllegalStateException("Invalid request - the item has been closed");
+    }
+
+    if (expectedItemsIterator == null) {
+      expectedItemsIterator = responses.values().iterator();
+    }
+
+    return expectedItemsIterator.hasNext();
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public ODataResponse next() {
+
+    if (current != null) {
+      current.close();
+    }
+
+    if (closed) {
+      throw new IllegalStateException("Invalid request - the item has been closed");
+    }
+
+    if (hasNext()) {
+      // consume item for condition above (like a counter ...)
+      expectedItemsIterator.next();
+    } else {
+      throw new NoSuchElementException("No item found");
+    }
+
+    final Map<String, Collection<String>> nextItemHeaders =
+            ODataBatchUtilities.nextItemHeaders(batchLineIterator, boundary);
+
+    if (nextItemHeaders.isEmpty()) {
+      throw new IllegalStateException("Expected item not found");
+    }
+
+    final Map.Entry<Integer, String> responseLine = ODataBatchUtilities.readResponseLine(batchLineIterator);
+    LOG.debug("Retrieved item response {}", responseLine);
+
+    final Map<String, Collection<String>> headers = ODataBatchUtilities.readHeaders(batchLineIterator);
+    LOG.debug("Retrieved item headers {}", headers);
+
+    Collection<String> contentId = nextItemHeaders.get(ODataBatchConstants.CHANGESET_CONTENT_ID_NAME);
+
+    if (contentId == null || contentId.isEmpty()) {
+      contentId = headers.get(ODataBatchConstants.CHANGESET_CONTENT_ID_NAME);
+
+      if (contentId == null || contentId.isEmpty()) {
+        throw new IllegalStateException("Content-ID is missing");
+      }
+    }
+
+    current = getResponse(contentId.iterator().next());
+
+    if (current == null) {
+      throw new IllegalStateException("Unexpected '" + contentId + "' item found");
+    }
+
+    current.initFromBatch(responseLine, headers, batchLineIterator, boundary);
+
+    if (current.getStatusCode() >= 400) {
+      // found error .... consume expeted items
+      while (expectedItemsIterator.hasNext()) {
+        expectedItemsIterator.next();
+      }
+    }
+
+    return current;
+  }
+
+  /**
+   * Unsupported operation.
+   */
+  @Override
+  public void remove() {
+    throw new UnsupportedOperationException("Not supported operation.");
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveImpl.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveImpl.java
new file mode 100644
index 0000000..fb348a4
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveImpl.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.client.core.communication.request.batch;
+
+import org.apache.olingo.client.api.communication.request.ODataBatchableRequest;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchRequest;
+import org.apache.olingo.client.api.communication.request.batch.ODataRetrieve;
+import org.apache.olingo.client.api.http.HttpMethod;
+import org.apache.olingo.client.core.communication.request.ODataRequestImpl;
+
+/**
+ * Retrieve request wrapper for the corresponding batch item.
+ */
+public class ODataRetrieveImpl extends AbstractODataBatchRequestItem
+        implements ODataRetrieve {
+
+  private final ODataRetrieveResponseItem expectedResItem;
+
+  /**
+   * Constructor.
+   *
+   * @param req batch request.
+   * @param expectedResItem expected batch response item.
+   */
+  ODataRetrieveImpl(final ODataBatchRequest req, final ODataRetrieveResponseItem expectedResItem) {
+    super(req);
+    this.expectedResItem = expectedResItem;
+  }
+
+  /**
+   * Close item.
+   */
+  @Override
+  protected void closeItem() {
+    // nop
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public ODataRetrieve setRequest(final ODataBatchableRequest request) {
+    if (!isOpen()) {
+      throw new IllegalStateException("Current batch item is closed");
+    }
+
+    if (((ODataRequestImpl) request).getMethod() != HttpMethod.GET) {
+      throw new IllegalArgumentException("Invalid request. Only GET method is allowed");
+    }
+
+    hasStreamedSomething = true;
+
+    // stream the request
+    streamRequestHeader(request);
+
+    // close before in order to avoid any further setRequest calls.
+    close();
+
+    // add request to the list
+    expectedResItem.addResponse(
+            ODataRetrieveResponseItem.RETRIEVE_CONTENT_ID, ((ODataRequestImpl) request).getResponseTemplate());
+
+    return this;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveResponseItem.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveResponseItem.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveResponseItem.java
new file mode 100644
index 0000000..4a30e2e
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/ODataRetrieveResponseItem.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.client.core.communication.request.batch;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import org.apache.olingo.client.api.communication.response.ODataResponse;
+
+/**
+ * Retrieve response wrapper for the corresponding batch item.
+ */
+public class ODataRetrieveResponseItem extends AbstractODataBatchResponseItem {
+
+  public static final String RETRIEVE_CONTENT_ID = "__RETRIEVE__";
+
+  /**
+   * Constructor.
+   */
+  public ODataRetrieveResponseItem() {
+    super(false);
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public boolean hasNext() {
+    if (closed) {
+      throw new IllegalStateException("Invalid request - the item has been closed");
+    }
+
+    if (expectedItemsIterator == null) {
+      expectedItemsIterator = responses.values().iterator();
+    }
+
+    return expectedItemsIterator.hasNext();
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public ODataResponse next() {
+    if (closed) {
+      throw new IllegalStateException("Invalid request - the item has been closed");
+    }
+
+    if (!hasNext()) {
+      throw new NoSuchElementException("No item found");
+    }
+
+    final Map.Entry<Integer, String> responseLine = ODataBatchUtilities.readResponseLine(batchLineIterator);
+    LOG.debug("Retrieved item response {}", responseLine);
+
+    final Map<String, Collection<String>> headers = ODataBatchUtilities.readHeaders(batchLineIterator);
+    LOG.debug("Retrieved item headers {}", headers);
+
+    return expectedItemsIterator.next().initFromBatch(responseLine, headers, batchLineIterator, boundary);
+  }
+
+  /**
+   * Unsupported operation.
+   */
+  @Override
+  public void remove() {
+    throw new UnsupportedOperationException("Operation not supported.");
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/V3BatchRequestFactoryImpl.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/V3BatchRequestFactoryImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/V3BatchRequestFactoryImpl.java
new file mode 100644
index 0000000..865de64
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/V3BatchRequestFactoryImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.client.core.communication.request.batch;
+
+import org.apache.olingo.client.api.ODataV3Client;
+import org.apache.olingo.client.api.communication.request.batch.V3BatchRequestFactory;
+
+public class V3BatchRequestFactoryImpl extends AbstractBatchRequestFactory
+        implements V3BatchRequestFactory {
+
+  private static final long serialVersionUID = -6271567229804128570L;
+
+  public V3BatchRequestFactoryImpl(final ODataV3Client client) {
+    super(client);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/V4BatchRequestFactoryImpl.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/V4BatchRequestFactoryImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/V4BatchRequestFactoryImpl.java
new file mode 100644
index 0000000..0e61d4c
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/batch/V4BatchRequestFactoryImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.client.core.communication.request.batch;
+
+import org.apache.olingo.client.api.ODataV4Client;
+import org.apache.olingo.client.api.communication.request.batch.V4BatchRequestFactory;
+
+public class V4BatchRequestFactoryImpl extends AbstractBatchRequestFactory
+        implements V4BatchRequestFactory {
+
+  private static final long serialVersionUID = 788349446729208639L;
+
+  public V4BatchRequestFactoryImpl(final ODataV4Client client) {
+    super(client);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/AbstractCUDRequestFactory.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/AbstractCUDRequestFactory.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/AbstractCUDRequestFactory.java
new file mode 100644
index 0000000..b5128e6
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/AbstractCUDRequestFactory.java
@@ -0,0 +1,197 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.client.core.communication.request.cud;
+
+import java.net.URI;
+import org.apache.olingo.client.api.ODataClient;
+import org.apache.olingo.client.api.communication.request.UpdateType;
+import org.apache.olingo.client.api.communication.request.cud.CUDRequestFactory;
+import org.apache.olingo.client.api.communication.request.cud.ODataDeleteRequest;
+import org.apache.olingo.client.api.communication.request.cud.ODataEntityCreateRequest;
+import org.apache.olingo.client.api.communication.request.cud.ODataEntityUpdateRequest;
+import org.apache.olingo.client.api.communication.request.cud.ODataLinkCreateRequest;
+import org.apache.olingo.client.api.communication.request.cud.ODataLinkUpdateRequest;
+import org.apache.olingo.client.api.communication.request.cud.ODataPropertyUpdateRequest;
+import org.apache.olingo.client.api.communication.request.cud.ODataValueUpdateRequest;
+import org.apache.olingo.client.api.domain.ODataEntity;
+import org.apache.olingo.client.api.domain.ODataLink;
+import org.apache.olingo.client.api.domain.ODataPrimitiveValue;
+import org.apache.olingo.client.api.domain.ODataProperty;
+import org.apache.olingo.client.api.http.HttpMethod;
+
+public abstract class AbstractCUDRequestFactory implements CUDRequestFactory {
+
+  private static final long serialVersionUID = -2723641791198745990L;
+
+  protected final ODataClient client;
+
+  protected AbstractCUDRequestFactory(final ODataClient client) {
+    this.client = client;
+  }
+
+  @Override
+  public ODataEntityCreateRequest getEntityCreateRequest(final URI targetURI, final ODataEntity entity) {
+    return new ODataEntityCreateRequestImpl(client, targetURI, entity);
+  }
+
+  @Override
+  public ODataEntityUpdateRequest getEntityUpdateRequest(
+          final URI targetURI, final UpdateType type, final ODataEntity changes) {
+
+    final ODataEntityUpdateRequest req;
+
+    if (client.getConfiguration().isUseXHTTPMethod()) {
+      req = new ODataEntityUpdateRequestImpl(client, HttpMethod.POST, targetURI, changes);
+      req.setXHTTPMethod(type.getMethod().name());
+    } else {
+      req = new ODataEntityUpdateRequestImpl(client, type.getMethod(), targetURI, changes);
+    }
+
+    return req;
+  }
+
+  @Override
+  public ODataEntityUpdateRequest getEntityUpdateRequest(final UpdateType type, final ODataEntity entity) {
+    if (entity.getEditLink() == null) {
+      throw new IllegalArgumentException("No edit link found");
+    }
+
+    final ODataEntityUpdateRequest req;
+
+    if (client.getConfiguration().isUseXHTTPMethod()) {
+      req = new ODataEntityUpdateRequestImpl(client, HttpMethod.POST, entity.getEditLink(), entity);
+      req.setXHTTPMethod(type.getMethod().name());
+    } else {
+      req = new ODataEntityUpdateRequestImpl(client, type.getMethod(), entity.getEditLink(), entity);
+    }
+
+    return req;
+  }
+
+  @Override
+  public ODataValueUpdateRequest getValueUpdateRequest(
+          final URI targetURI, final UpdateType type, final ODataPrimitiveValue value) {
+
+    final ODataValueUpdateRequest req;
+
+    if (client.getConfiguration().isUseXHTTPMethod()) {
+      req = new ODataValueUpdateRequestImpl(client, HttpMethod.POST, targetURI, value);
+      req.setXHTTPMethod(type.getMethod().name());
+    } else {
+      req = new ODataValueUpdateRequestImpl(client, type.getMethod(), targetURI, value);
+    }
+
+    return req;
+  }
+
+  @Override
+  public ODataPropertyUpdateRequest getPropertyPrimitiveValueUpdateRequest(
+          final URI targetURI, final ODataProperty property) {
+
+    if (!property.hasPrimitiveValue()) {
+      throw new IllegalArgumentException("A primitive value is required");
+    }
+
+    final ODataPropertyUpdateRequest req;
+
+    if (client.getConfiguration().isUseXHTTPMethod()) {
+      req = new ODataPropertyUpdateRequestImpl(client, HttpMethod.POST, targetURI, property);
+      req.setXHTTPMethod(HttpMethod.PUT.name());
+    } else {
+      req = new ODataPropertyUpdateRequestImpl(client, HttpMethod.PUT, targetURI, property);
+    }
+
+    return req;
+  }
+
+  @Override
+  public ODataPropertyUpdateRequest getPropertyComplexValueUpdateRequest(
+          final URI targetURI, final UpdateType type, final ODataProperty property) {
+
+    if (!property.hasComplexValue()) {
+      throw new IllegalArgumentException("A complex value is required");
+    }
+
+    final ODataPropertyUpdateRequest req;
+
+    if (client.getConfiguration().isUseXHTTPMethod()) {
+      req = new ODataPropertyUpdateRequestImpl(client, HttpMethod.POST, targetURI, property);
+      req.setXHTTPMethod(type.getMethod().name());
+    } else {
+      req = new ODataPropertyUpdateRequestImpl(client, type.getMethod(), targetURI, property);
+    }
+
+    return req;
+  }
+
+  @Override
+  public ODataPropertyUpdateRequest getPropertyCollectionValueUpdateRequest(
+          final URI targetURI, final ODataProperty property) {
+
+    if (!property.hasCollectionValue()) {
+      throw new IllegalArgumentException("A collection value is required");
+    }
+
+    final ODataPropertyUpdateRequest req;
+
+    if (client.getConfiguration().isUseXHTTPMethod()) {
+      req = new ODataPropertyUpdateRequestImpl(client, HttpMethod.POST, targetURI, property);
+      req.setXHTTPMethod(HttpMethod.PUT.name());
+    } else {
+      req = new ODataPropertyUpdateRequestImpl(client, HttpMethod.PUT, targetURI, property);
+    }
+
+    return req;
+  }
+
+  @Override
+  public ODataLinkCreateRequest getLinkCreateRequest(final URI targetURI, final ODataLink link) {
+    return new ODataLinkCreateRequestImpl(client, targetURI, link);
+  }
+
+  @Override
+  public ODataLinkUpdateRequest getLinkUpdateRequest(
+          final URI targetURI, final UpdateType type, final ODataLink link) {
+
+    final ODataLinkUpdateRequest req;
+
+    if (client.getConfiguration().isUseXHTTPMethod()) {
+      req = new ODataLinkUpdateRequestImpl(client, HttpMethod.POST, targetURI, link);
+      req.setXHTTPMethod(type.getMethod().name());
+    } else {
+      req = new ODataLinkUpdateRequestImpl(client, type.getMethod(), targetURI, link);
+    }
+
+    return req;
+  }
+
+  @Override
+  public ODataDeleteRequest getDeleteRequest(final URI targetURI) {
+    final ODataDeleteRequest req;
+
+    if (client.getConfiguration().isUseXHTTPMethod()) {
+      req = new ODataDeleteRequestImpl(client, HttpMethod.POST, targetURI);
+      req.setXHTTPMethod(HttpMethod.DELETE.name());
+    } else {
+      req = new ODataDeleteRequestImpl(client, HttpMethod.DELETE, targetURI);
+    }
+
+    return req;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata4/blob/d3b05e01/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/ODataDeleteRequestImpl.java
----------------------------------------------------------------------
diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/ODataDeleteRequestImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/ODataDeleteRequestImpl.java
new file mode 100644
index 0000000..3010867
--- /dev/null
+++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/cud/ODataDeleteRequestImpl.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.client.core.communication.request.cud;
+
+import java.io.InputStream;
+import java.net.URI;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.olingo.client.api.ODataClient;
+import org.apache.olingo.client.api.communication.request.ODataBatchableRequest;
+import org.apache.olingo.client.api.communication.request.cud.ODataDeleteRequest;
+import org.apache.olingo.client.api.communication.response.ODataDeleteResponse;
+import org.apache.olingo.client.api.format.ODataPubFormat;
+import org.apache.olingo.client.api.http.HttpMethod;
+import org.apache.olingo.client.core.communication.request.AbstractODataBasicRequest;
+import org.apache.olingo.client.core.communication.response.ODataResponseImpl;
+
+/**
+ * This class implements an OData delete request.
+ */
+public class ODataDeleteRequestImpl extends AbstractODataBasicRequest<ODataDeleteResponse, ODataPubFormat>
+        implements ODataDeleteRequest, ODataBatchableRequest {
+
+  /**
+   * Constructor.
+   *
+   * @param odataClient client instance getting this request
+   * @param method HTTP method to be used
+   * @param uri URI of the entity to be deleted.
+   */
+  ODataDeleteRequestImpl(final ODataClient odataClient, final HttpMethod method, final URI uri) {
+    super(odataClient, ODataPubFormat.class, method, uri);
+  }
+
+  /**
+   * {@inheritDoc }
+   * <p>
+   * No payload: null will be returned.
+   */
+  @Override
+  protected InputStream getPayload() {
+    return null;
+  }
+
+  /**
+   * {@inheritDoc }
+   */
+  @Override
+  public ODataDeleteResponse execute() {
+    return new ODataDeleteResponseImpl(httpClient, doExecute());
+  }
+
+  /**
+   * Response class about an ODataDeleteRequest.
+   */
+  private class ODataDeleteResponseImpl extends ODataResponseImpl implements ODataDeleteResponse {
+
+    /**
+     * Constructor.
+     * <p>
+     * Just to create response templates to be initialized from batch.
+     */
+    private ODataDeleteResponseImpl() {
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param client HTTP client.
+     * @param res HTTP response.
+     */
+    private ODataDeleteResponseImpl(final HttpClient client, final HttpResponse res) {
+      super(client, res);
+      this.close();
+    }
+  }
+}