You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by mi...@apache.org on 2014/10/14 20:34:52 UTC

[05/39] git commit: Batch Parser

Batch Parser

Signed-off-by: Christian Amend <ch...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata2/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata2/commit/6eca235e
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata2/tree/6eca235e
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata2/diff/6eca235e

Branch: refs/heads/Olingo-129_PocJpaDataStore
Commit: 6eca235ea24a19dbd578109b6590f435fe16e2d4
Parents: 4966ebe
Author: Christian Holzer <c....@sap.com>
Authored: Wed Aug 20 17:53:10 2014 +0200
Committer: Christian Amend <ch...@apache.org>
Committed: Tue Sep 23 14:47:37 2014 +0200

----------------------------------------------------------------------
 .../olingo/odata2/api/batch/BatchException.java |   6 +-
 .../odata2/api/batch/BatchParserResult.java     |   5 +
 .../odata2/api/batch/BatchRequestPart.java      |   2 +-
 .../odata2/api/batch/BatchResponsePart.java     |   2 +-
 .../api/client/batch/BatchSingleResponse.java   |   4 +-
 .../odata2/core/batch/BatchRequestParser.java   | 614 -------------------
 .../odata2/core/batch/BatchResponseParser.java  | 356 -----------
 .../odata2/core/batch/v2/BatchBodyPart.java     | 155 +++++
 .../odata2/core/batch/v2/BatchChangeSet.java    |  55 ++
 .../odata2/core/batch/v2/BatchParser.java       | 130 ++++
 .../odata2/core/batch/v2/BatchParserCommon.java | 414 +++++++++++++
 .../olingo/odata2/core/batch/v2/BatchPart.java  |  29 +
 .../core/batch/v2/BatchQueryOperation.java      |  82 +++
 .../batch/v2/BatchRequestTransformator.java     | 253 ++++++++
 .../batch/v2/BatchResponseTransformator.java    | 134 ++++
 .../core/batch/v2/BatchTransformator.java       |  30 +
 .../core/batch/v2/BatchTransformatorCommon.java |  84 +++
 .../v2/BufferedReaderIncludingLineEndings.java  | 220 +++++++
 .../odata2/core/ep/ProviderFacadeImpl.java      |   7 +-
 .../src/main/resources/i18n.properties          |   1 +
 .../core/batch/BatchParserCommonTest.java       |  99 +++
 .../core/batch/BatchRequestParserTest.java      | 526 +++++++++++++++-
 .../odata2/core/batch/BatchRequestTest.java     |  48 +-
 .../core/batch/BatchResponseParserTest.java     |  34 +-
 .../odata2/core/batch/BatchResponseTest.java    |  13 +-
 .../core/batch/BatchResponseWriterTest.java     |   2 +-
 .../batch/BatchTransformatorCommonTest.java     |  95 +++
 .../BufferedReaderIncludingLineEndingsTest.java | 452 ++++++++++++++
 .../src/test/resources/batchWithPost.batch      |   1 +
 .../odata2/fit/client/ClientBatchTest.java      |   2 +
 .../fit/client/ClientDeltaResponseTest.java     |   2 +
 .../src/test/resources/batchWithContentId.batch |   2 +
 .../resources/batchWithContentIdPart2.batch     |   6 +-
 .../src/test/resources/changeset.batch          |   2 +
 34 files changed, 2826 insertions(+), 1041 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchException.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchException.java b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchException.java
index 647b071..1171719 100644
--- a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchException.java
+++ b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchException.java
@@ -43,7 +43,11 @@ public class BatchException extends ODataMessageException {
   /** MISSING_CLOSE_DELIMITER requires 1 content value ('line number') */
   public static final MessageReference MISSING_CLOSE_DELIMITER = createMessageReference(BatchException.class,
       "MISSING_CLOSE_DELIMITER");
-
+  
+  /** MISSONG MANDATORY HEADER requires 1 content value ('header name') */
+  public static final MessageReference MISSING_MANDATORY_HEADER = createMessageReference(BatchException.class, 
+      "MISSING_MANDATORY_HEADER");
+  
   /** INVALID_QUERY_OPERATION_METHOD requires 1 content value ('line number') */
   public static final MessageReference INVALID_QUERY_OPERATION_METHOD = createMessageReference(BatchException.class,
       "INVALID_QUERY_OPERATION_METHOD");

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchParserResult.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchParserResult.java b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchParserResult.java
new file mode 100644
index 0000000..e11b69e
--- /dev/null
+++ b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchParserResult.java
@@ -0,0 +1,5 @@
+package org.apache.olingo.odata2.api.batch;
+
+public interface BatchParserResult {
+
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchRequestPart.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchRequestPart.java b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchRequestPart.java
index 5e3e2f2..5f76a36 100644
--- a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchRequestPart.java
+++ b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchRequestPart.java
@@ -26,7 +26,7 @@ import org.apache.olingo.odata2.api.processor.ODataRequest;
  * A BatchPart
  * <p> BatchPart represents a distinct MIME part of a Batch Request body. It can be ChangeSet or Query Operation
  */
-public interface BatchRequestPart {
+public interface BatchRequestPart extends BatchParserResult {
 
   /**
    * Get the info if a BatchPart is a ChangeSet

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchResponsePart.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchResponsePart.java b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchResponsePart.java
index dfafbdb..6133104 100644
--- a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchResponsePart.java
+++ b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchResponsePart.java
@@ -29,7 +29,7 @@ import org.apache.olingo.odata2.api.rt.RuntimeDelegate;
  * response to a retrieve request
  * 
  */
-public abstract class BatchResponsePart {
+public abstract class BatchResponsePart implements BatchParserResult {
 
   /**
    * Get responses. If a BatchResponsePart is a response to a retrieve request, the list consists of one response.

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchSingleResponse.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchSingleResponse.java b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchSingleResponse.java
index dc8c9b7..ddb3c02 100644
--- a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchSingleResponse.java
+++ b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchSingleResponse.java
@@ -21,12 +21,14 @@ package org.apache.olingo.odata2.api.client.batch;
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.olingo.odata2.api.batch.BatchParserResult;
+
 /**
  * A BatchSingleResponse
  * <p> BatchSingleResponse represents a single response of a Batch Response body. It can be a response to a change
  * request of ChangeSet or a response to a retrieve request
  */
-public interface BatchSingleResponse {
+public interface BatchSingleResponse extends BatchParserResult {
   /**
    * @return a result code of the attempt to understand and satisfy the request
    */

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestParser.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestParser.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestParser.java
deleted file mode 100644
index 6ac1445..0000000
--- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestParser.java
+++ /dev/null
@@ -1,614 +0,0 @@
-/*******************************************************************************
- * 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.odata2.core.batch;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Scanner;
-import java.util.Set;
-import java.util.regex.MatchResult;
-import java.util.regex.Pattern;
-
-import org.apache.olingo.odata2.api.batch.BatchException;
-import org.apache.olingo.odata2.api.batch.BatchRequestPart;
-import org.apache.olingo.odata2.api.commons.HttpContentType;
-import org.apache.olingo.odata2.api.commons.HttpHeaders;
-import org.apache.olingo.odata2.api.commons.ODataHttpMethod;
-import org.apache.olingo.odata2.api.ep.EntityProviderBatchProperties;
-import org.apache.olingo.odata2.api.processor.ODataRequest;
-import org.apache.olingo.odata2.api.processor.ODataRequest.ODataRequestBuilder;
-import org.apache.olingo.odata2.api.uri.PathInfo;
-import org.apache.olingo.odata2.api.uri.PathSegment;
-import org.apache.olingo.odata2.core.ODataPathSegmentImpl;
-import org.apache.olingo.odata2.core.PathInfoImpl;
-import org.apache.olingo.odata2.core.commons.Decoder;
-import org.apache.olingo.odata2.core.exception.ODataRuntimeException;
-
-/**
- *  
- */
-public class BatchRequestParser {
-  private static final String LF = "\n";
-  private static final String REG_EX_OPTIONAL_WHITESPACE = "\\s?";
-  private static final String REG_EX_ZERO_OR_MORE_WHITESPACES = "\\s*";
-  private static final String ANY_CHARACTERS = ".*";
-
-  private static final Pattern REG_EX_BLANK_LINE = Pattern.compile("(|" + REG_EX_ZERO_OR_MORE_WHITESPACES + ")");
-  private static final Pattern REG_EX_HEADER = Pattern.compile("([a-zA-Z\\-]+):" + REG_EX_OPTIONAL_WHITESPACE + "(.*)"
-      + REG_EX_ZERO_OR_MORE_WHITESPACES);
-  private static final Pattern REG_EX_VERSION = Pattern.compile("(?:HTTP/[0-9]\\.[0-9])");
-  private static final Pattern REG_EX_ANY_BOUNDARY_STRING = Pattern.compile("--" + ANY_CHARACTERS
-      + REG_EX_ZERO_OR_MORE_WHITESPACES);
-  private static final Pattern REG_EX_REQUEST_LINE = Pattern.compile("(GET|POST|PUT|DELETE|MERGE|PATCH)\\s(.*)\\s?"
-      + REG_EX_VERSION + REG_EX_ZERO_OR_MORE_WHITESPACES);
-  private static final Pattern REG_EX_BOUNDARY_PARAMETER = Pattern.compile(REG_EX_OPTIONAL_WHITESPACE
-      + "boundary=(\".*\"|.*)" + REG_EX_ZERO_OR_MORE_WHITESPACES);
-  private static final Pattern REG_EX_CONTENT_TYPE = Pattern.compile(REG_EX_OPTIONAL_WHITESPACE
-      + HttpContentType.MULTIPART_MIXED);
-  private static final Pattern REG_EX_QUERY_PARAMETER = Pattern.compile("((?:\\$|)[^=]+)=([^=]+)");
-
-  private static final String REG_EX_BOUNDARY =
-      "([a-zA-Z0-9_\\-\\.'\\+]{1,70})|\"([a-zA-Z0-9_\\-\\.'\\+\\s\\" +
-          "(\\),/:=\\?]{1,69}[a-zA-Z0-9_\\-\\.'\\+\\(\\),/:=\\?])\""; // See RFC 2046
-
-  private String baseUri;
-  private PathInfo batchRequestPathInfo;
-  private String contentTypeMime;
-  private String boundary;
-  private String currentMimeHeaderContentId;
-  private int currentLineNumber = 0;
-  private final static Set<String> HTTP_CHANGESET_METHODS;
-  private final static Set<String> HTTP_BATCH_METHODS;
-
-  static {
-    HashSet<String> httpChangesetMethods = new HashSet<String>();
-    httpChangesetMethods.add("POST");
-    httpChangesetMethods.add("PUT");
-    httpChangesetMethods.add("DELETE");
-    httpChangesetMethods.add("MERGE");
-    httpChangesetMethods.add("PATCH");
-    HTTP_CHANGESET_METHODS = Collections.unmodifiableSet(httpChangesetMethods);
-
-    HashSet<String> httpBatchMethods = new HashSet<String>();
-    httpBatchMethods.add("GET");
-    HTTP_BATCH_METHODS = Collections.unmodifiableSet(httpBatchMethods);
-  }
-
-  public BatchRequestParser(final String contentType, final EntityProviderBatchProperties properties) {
-    contentTypeMime = contentType;
-    batchRequestPathInfo = properties.getPathInfo();
-  }
-
-  public List<BatchRequestPart> parse(final InputStream in) throws BatchException {
-    Scanner scanner = new Scanner(in, BatchHelper.DEFAULT_ENCODING);
-    scanner.useDelimiter(LF);
-    baseUri = getBaseUri();
-    List<BatchRequestPart> requestList;
-    try {
-      requestList = parseBatchRequest(scanner);
-    } finally {// NOPMD (suppress DoNotThrowExceptionInFinally)
-      scanner.close();
-      try {
-        in.close();
-      } catch (IOException e) {
-        throw new ODataRuntimeException(e);
-      }
-    }
-    return requestList;
-  }
-
-  private List<BatchRequestPart> parseBatchRequest(final Scanner scanner) throws BatchException {
-    List<BatchRequestPart> requests = new LinkedList<BatchRequestPart>();
-    if (contentTypeMime != null) {
-      boundary = getBoundary(contentTypeMime);
-      parsePreamble(scanner);
-      final String closeDelimiter = "--" + boundary + "--" + REG_EX_ZERO_OR_MORE_WHITESPACES;
-      while (scanner.hasNext() && !scanner.hasNext(closeDelimiter)) {
-        requests.add(parseMultipart(scanner, boundary, false));
-        parseOptionalLine(scanner);
-      }
-      if (scanner.hasNext(closeDelimiter)) {
-        scanner.next(closeDelimiter);
-        currentLineNumber++;
-      } else {
-        throw new BatchException(BatchException.MISSING_CLOSE_DELIMITER.addContent(currentLineNumber));
-      }
-    } else {
-      throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
-    }
-    return requests;
-  }
-
-  // The method parses additional information prior to the first boundary delimiter line
-  private void parsePreamble(final Scanner scanner) {
-    while (scanner.hasNext() && !scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
-      scanner.next();
-      currentLineNumber++;
-    }
-  }
-
-  private BatchRequestPart parseMultipart(final Scanner scanner, final String boundary, final boolean isChangeSet)
-      throws BatchException {
-
-    if (scanner.hasNext("--" + boundary + REG_EX_ZERO_OR_MORE_WHITESPACES)) {
-      scanner.next();
-      currentLineNumber++;
-      Map<String, String> mimeHeaders = parseHeaders(scanner);
-      currentMimeHeaderContentId = mimeHeaders.get(BatchHelper.HTTP_CONTENT_ID.toLowerCase(Locale.ENGLISH));
-
-      String contentType = mimeHeaders.get(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ENGLISH));
-      if (contentType == null) {
-        throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
-      }
-      if (isChangeSet) {
-        return parseBatchRequestPartInChangeset(scanner, boundary, mimeHeaders, contentType);
-      } else {
-        return parseBatchRequestPart(scanner, boundary, mimeHeaders, contentType);
-      }
-    } else if (scanner.hasNext(boundary + REG_EX_ZERO_OR_MORE_WHITESPACES)) {
-      currentLineNumber++;
-      throw new BatchException(BatchException.INVALID_BOUNDARY_DELIMITER.addContent(currentLineNumber));
-    } else if (scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
-      currentLineNumber++;
-      throw new BatchException(BatchException.NO_MATCH_WITH_BOUNDARY_STRING.addContent(boundary).addContent(
-          currentLineNumber));
-    } else {
-      currentLineNumber++;
-      throw new BatchException(BatchException.MISSING_BOUNDARY_DELIMITER.addContent(currentLineNumber));
-    }
-  }
-
-  private BatchRequestPart parseBatchRequestPart(final Scanner scanner, final String boundary,
-      final Map<String, String> mimeHeaders,
-      final String contentType) throws BatchException {
-    if (HttpContentType.APPLICATION_HTTP.equalsIgnoreCase(contentType)) {
-      validateEncoding(mimeHeaders.get(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING.toLowerCase(Locale.ENGLISH)));
-      parseNewLine(scanner);// mandatory
-      List<ODataRequest> requests = new ArrayList<ODataRequest>(1);
-      requests.add(parseRequest(scanner, false, boundary));
-      return new BatchRequestPartImpl(false, requests);
-    } else if (contentType.matches(REG_EX_OPTIONAL_WHITESPACE + HttpContentType.MULTIPART_MIXED + ANY_CHARACTERS)) {
-      String changeSetBoundary = getBoundary(contentType);
-      if (boundary.equals(changeSetBoundary)) {
-        throw new BatchException(BatchException.INVALID_CHANGESET_BOUNDARY.addContent(currentLineNumber));
-      }
-      List<ODataRequest> changeSetRequests = new LinkedList<ODataRequest>();
-      parseNewLine(scanner);// mandatory
-      Pattern changeSetCloseDelimiter =
-          Pattern.compile("--" + changeSetBoundary + "--" + REG_EX_ZERO_OR_MORE_WHITESPACES);
-      while (!scanner.hasNext(changeSetCloseDelimiter)) {
-        BatchRequestPart part = parseMultipart(scanner, changeSetBoundary, true);
-        changeSetRequests.addAll(part.getRequests());
-      }
-      scanner.next(changeSetCloseDelimiter);
-      currentLineNumber++;
-      return new BatchRequestPartImpl(true, changeSetRequests);
-    } else {
-      throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED
-          + " or " + HttpContentType.APPLICATION_HTTP));
-    }
-  }
-
-  private BatchRequestPart parseBatchRequestPartInChangeset(final Scanner scanner, final String boundary,
-      final Map<String, String> mimeHeaders,
-      final String contentType) throws BatchException {
-    if (HttpContentType.APPLICATION_HTTP.equalsIgnoreCase(contentType)) {
-      validateEncoding(mimeHeaders.get(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING.toLowerCase(Locale.ENGLISH)));
-      parseNewLine(scanner);// mandatory
-      List<ODataRequest> requests = new ArrayList<ODataRequest>(1);
-      requests.add(parseRequest(scanner, true, boundary));
-      return new BatchRequestPartImpl(false, requests);
-    } else {
-      throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.APPLICATION_HTTP));
-    }
-  }
-
-  private ODataRequest parseRequest(final Scanner scanner, final boolean isChangeSet, final String boundary)
-      throws BatchException {
-    if (scanner.hasNext(REG_EX_REQUEST_LINE)) {
-      scanner.next(REG_EX_REQUEST_LINE);
-      currentLineNumber++;
-      final String method;
-      final String uri;
-      MatchResult result = scanner.match();
-      if (result.groupCount() == 2) {
-        method = result.group(1);
-        uri = result.group(2).trim();
-      } else {
-        currentLineNumber++;
-        throw new BatchException(BatchException.INVALID_REQUEST_LINE.addContent(scanner.next()).addContent(
-            currentLineNumber));
-      }
-      PathInfo pathInfo = parseRequestUri(uri);
-      Map<String, String> queryParameters = parseQueryParameters(uri);
-      if (isChangeSet) {
-        if (!HTTP_CHANGESET_METHODS.contains(method)) {
-          throw new BatchException(BatchException.INVALID_CHANGESET_METHOD.addContent(currentLineNumber));
-        }
-      } else if (!HTTP_BATCH_METHODS.contains(method)) {
-        throw new BatchException(BatchException.INVALID_QUERY_OPERATION_METHOD.addContent(currentLineNumber));
-      }
-      ODataHttpMethod httpMethod = ODataHttpMethod.valueOf(method);
-      Map<String, List<String>> headers = parseRequestHeaders(scanner, boundary);
-      if (currentMimeHeaderContentId != null) {
-        List<String> headerList = new ArrayList<String>();
-        headerList.add(currentMimeHeaderContentId);
-        headers.put(BatchHelper.MIME_HEADER_CONTENT_ID.toLowerCase(Locale.ENGLISH), headerList);
-      }
-
-      String contentType = getContentTypeHeader(headers);
-      List<String> acceptHeaders = getAcceptHeader(headers);
-      List<Locale> acceptLanguages = getAcceptLanguageHeader(headers);
-      InputStream body = new ByteArrayInputStream(new byte[0]);
-      if (isChangeSet) {
-        body = parseBody(scanner);
-      }
-
-      ODataRequestBuilder requestBuilder = ODataRequest.method(httpMethod)
-          .queryParameters(queryParameters)
-          .requestHeaders(headers)
-          .pathInfo(pathInfo)
-          .acceptableLanguages(acceptLanguages)
-          .body(body)
-          .acceptHeaders(acceptHeaders);
-
-      if (contentType != null) {
-        requestBuilder = requestBuilder.contentType(contentType);
-      }
-      return requestBuilder.build();
-    } else {
-      currentLineNumber++;
-      throw new BatchException(BatchException.INVALID_REQUEST_LINE.addContent(scanner.next()).addContent(
-          currentLineNumber));
-    }
-
-  }
-
-  private Map<String, List<String>> parseRequestHeaders(final Scanner scanner, final String boundary)
-      throws BatchException {
-    Map<String, List<String>> headers = new HashMap<String, List<String>>();
-    while (scanner.hasNext()
-        && !scanner.hasNext(REG_EX_BLANK_LINE)
-        && !scanner.hasNext("--" + boundary + REG_EX_ZERO_OR_MORE_WHITESPACES)) {
-      if (scanner.hasNext(REG_EX_HEADER)) {
-        scanner.next(REG_EX_HEADER);
-        currentLineNumber++;
-        MatchResult result = scanner.match();
-        if (result.groupCount() == 2) {
-          String headerName = result.group(1).trim().toLowerCase(Locale.ENGLISH);
-          String headerValue = result.group(2).trim();
-          if (HttpHeaders.ACCEPT.equalsIgnoreCase(headerName)) {
-            List<String> acceptHeaders = parseAcceptHeaders(headerValue);
-            headers.put(headerName, acceptHeaders);
-          } else if (HttpHeaders.ACCEPT_LANGUAGE.equalsIgnoreCase(headerName)) {
-            List<String> acceptLanguageHeaders = parseAcceptableLanguages(headerValue);
-            headers.put(headerName, acceptLanguageHeaders);
-          } else if (!BatchHelper.HTTP_CONTENT_ID.equalsIgnoreCase(headerName)) {
-            if (headers.containsKey(headerName)) {
-              headers.get(headerName).add(headerValue);
-            } else {
-              List<String> headerList = new ArrayList<String>();
-              headerList.add(headerValue);
-              headers.put(headerName, headerList);
-            }
-          } else {
-            List<String> headerList = new ArrayList<String>();
-            headerList.add(headerValue);
-            headers.put(BatchHelper.REQUEST_HEADER_CONTENT_ID.toLowerCase(Locale.ENGLISH), headerList);
-          }
-        }
-      } else {
-        currentLineNumber++;
-        throw new BatchException(BatchException.INVALID_HEADER.addContent(scanner.next())
-            .addContent(currentLineNumber));
-      }
-    }
-    return headers;
-  }
-
-  private PathInfo parseRequestUri(final String uri) throws BatchException {
-    PathInfoImpl pathInfo = new PathInfoImpl();
-    pathInfo.setServiceRoot(batchRequestPathInfo.getServiceRoot());
-    pathInfo.setPrecedingPathSegment(batchRequestPathInfo.getPrecedingSegments());
-    final String odataPathSegmentsAsString;
-    final String queryParametersAsString;
-    try {
-      Scanner uriScanner = new Scanner(uri);
-      uriScanner.useDelimiter(LF);
-      URI uriObject = new URI(uri);
-      if (uriObject.isAbsolute()) {
-        Pattern regexRequestUri = Pattern.compile(baseUri + "/([^/][^?]*)(\\?.*)?");
-        if (uriScanner.hasNext(regexRequestUri)) {
-          uriScanner.next(regexRequestUri);
-          MatchResult result = uriScanner.match();
-          if (result.groupCount() == 2) {
-            odataPathSegmentsAsString = result.group(1);
-            queryParametersAsString = result.group(2) != null ? result.group(2) : "";
-          } else {
-            uriScanner.close();
-            throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber));
-          }
-        } else {
-          uriScanner.close();
-          throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber));
-        }
-      } else {
-        Pattern regexRequestUri = Pattern.compile("([^/][^?]*)(\\?.*)?");
-        if (uriScanner.hasNext(regexRequestUri)) {
-          uriScanner.next(regexRequestUri);
-          MatchResult result = uriScanner.match();
-          if (result.groupCount() == 2) {
-            odataPathSegmentsAsString = result.group(1);
-            queryParametersAsString = result.group(2) != null ? result.group(2) : "";
-          } else {
-            uriScanner.close();
-            throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber));
-          }
-        } else if (uriScanner.hasNext("/(.*)")) {
-          uriScanner.close();
-          throw new BatchException(BatchException.UNSUPPORTED_ABSOLUTE_PATH.addContent(currentLineNumber));
-        } else {
-          uriScanner.close();
-          throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber));
-        }
-
-      }
-      uriScanner.close();
-      pathInfo.setODataPathSegment(parseODataPathSegments(odataPathSegmentsAsString));
-      if (!odataPathSegmentsAsString.startsWith("$")) {
-        String requestUri = baseUri + "/" + odataPathSegmentsAsString + queryParametersAsString;
-        pathInfo.setRequestUri(new URI(requestUri));
-      }
-      return pathInfo;
-    } catch (URISyntaxException e) {
-      throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber), e);
-    }
-
-  }
-
-  private Map<String, String> parseQueryParameters(final String uri) throws BatchException {
-    Scanner uriScanner = new Scanner(uri);
-    uriScanner.useDelimiter("\n");
-    Map<String, String> queryParametersMap = new HashMap<String, String>();
-    Pattern regex = Pattern.compile("(?:" + baseUri + "/)?" + "[^?]+" + "\\?(.*)");
-    if (uriScanner.hasNext(regex)) {
-      uriScanner.next(regex);
-      MatchResult uriResult = uriScanner.match();
-      if (uriResult.groupCount() == 1) {
-        String queryParams = uriResult.group(1);
-        Scanner queryParamsScanner = new Scanner(queryParams);
-        queryParamsScanner.useDelimiter("&");
-        while (queryParamsScanner.hasNext(REG_EX_QUERY_PARAMETER)) {
-          queryParamsScanner.next(REG_EX_QUERY_PARAMETER);
-          MatchResult result = queryParamsScanner.match();
-          if (result.groupCount() == 2) {
-            String systemQueryOption = result.group(1);
-            String value = result.group(2);
-            queryParametersMap.put(systemQueryOption, Decoder.decode(value));
-          } else {
-            queryParamsScanner.close();
-            throw new BatchException(BatchException.INVALID_QUERY_PARAMETER);
-          }
-        }
-        queryParamsScanner.close();
-
-      } else {
-        uriScanner.close();
-        throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber));
-      }
-    }
-    uriScanner.close();
-    return queryParametersMap;
-  }
-
-  private List<PathSegment> parseODataPathSegments(final String odataPathSegmentsAsString) {
-    Scanner pathSegmentScanner = new Scanner(odataPathSegmentsAsString);
-    pathSegmentScanner.useDelimiter("/");
-    List<PathSegment> odataPathSegments = new ArrayList<PathSegment>();
-    while (pathSegmentScanner.hasNext()) {
-      odataPathSegments.add(new ODataPathSegmentImpl(pathSegmentScanner.next(), null));
-    }
-    pathSegmentScanner.close();
-    return odataPathSegments;
-  }
-
-  private List<String> parseAcceptHeaders(final String headerValue) throws BatchException {
-    return AcceptParser.parseAcceptHeaders(headerValue);
-  }
-
-  private List<String> parseAcceptableLanguages(final String headerValue) throws BatchException {
-    return AcceptParser.parseAcceptableLanguages(headerValue);
-  }
-
-  private InputStream parseBody(final Scanner scanner) {
-    StringBuilder body = null;
-    final InputStream requestBody;
-
-    while (scanner.hasNext() && !scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
-      if (!scanner.hasNext(REG_EX_ZERO_OR_MORE_WHITESPACES)) {
-        if (body == null) {
-          body = new StringBuilder(scanner.next());
-        } else {
-          body.append(LF).append(scanner.next());
-        }
-      } else {
-        scanner.next();
-      }
-      currentLineNumber++;
-    }
-
-    if (body != null) {
-      requestBody = new ByteArrayInputStream(BatchHelper.getBytes(body.toString()));
-    } else {
-      requestBody = new ByteArrayInputStream(new byte[0]);
-    }
-    return requestBody;
-  }
-
-  private String getBoundary(final String contentType) throws BatchException {
-    Scanner contentTypeScanner = new Scanner(contentType);
-    contentTypeScanner.useDelimiter(";\\s?");
-    if (contentTypeScanner.hasNext(REG_EX_CONTENT_TYPE)) {
-      contentTypeScanner.next(REG_EX_CONTENT_TYPE);
-    } else {
-      contentTypeScanner.close();
-      throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED));
-    }
-    if (contentTypeScanner.hasNext(REG_EX_BOUNDARY_PARAMETER)) {
-      contentTypeScanner.next(REG_EX_BOUNDARY_PARAMETER);
-      MatchResult result = contentTypeScanner.match();
-      contentTypeScanner.close();
-      if (result.groupCount() == 1 && result.group(1).trim().matches(REG_EX_BOUNDARY)) {
-        return trimQuota(result.group(1).trim());
-      } else {
-        throw new BatchException(BatchException.INVALID_BOUNDARY);
-      }
-    } else {
-      contentTypeScanner.close();
-      throw new BatchException(BatchException.MISSING_PARAMETER_IN_CONTENT_TYPE);
-    }
-  }
-
-  private void validateEncoding(final String encoding) throws BatchException {
-    if (!BatchHelper.BINARY_ENCODING.equalsIgnoreCase(encoding)) {
-      throw new BatchException(BatchException.INVALID_CONTENT_TRANSFER_ENCODING);
-    }
-  }
-
-  private Map<String, String> parseHeaders(final Scanner scanner) throws BatchException {
-    Map<String, String> headers = new HashMap<String, String>();
-    while (scanner.hasNext() && !(scanner.hasNext(REG_EX_BLANK_LINE))) {
-      if (scanner.hasNext(REG_EX_HEADER)) {
-        scanner.next(REG_EX_HEADER);
-        currentLineNumber++;
-        MatchResult result = scanner.match();
-        if (result.groupCount() == 2) {
-          String headerName = result.group(1).trim().toLowerCase(Locale.ENGLISH);
-          String headerValue = result.group(2).trim();
-          headers.put(headerName, headerValue);
-        }
-      } else {
-        throw new BatchException(BatchException.INVALID_HEADER.addContent(scanner.next()));
-      }
-    }
-    return headers;
-  }
-
-  private void parseNewLine(final Scanner scanner) throws BatchException {
-    if (scanner.hasNext() && scanner.hasNext(REG_EX_BLANK_LINE)) {
-      scanner.next();
-      currentLineNumber++;
-    } else {
-      currentLineNumber++;
-      if (scanner.hasNext()) {
-        throw new BatchException(BatchException.MISSING_BLANK_LINE.addContent(scanner.next()).addContent(
-            currentLineNumber));
-      } else {
-        throw new BatchException(BatchException.TRUNCATED_BODY.addContent(currentLineNumber));
-
-      }
-    }
-  }
-
-  private void parseOptionalLine(final Scanner scanner) throws BatchException {
-    while (scanner.hasNext() && scanner.hasNext(REG_EX_BLANK_LINE)) {
-      scanner.next();
-      currentLineNumber++;
-    }
-  }
-
-  private String getBaseUri() throws BatchException {
-    if (batchRequestPathInfo != null) {
-      if (batchRequestPathInfo.getServiceRoot() != null) {
-        String baseUri = batchRequestPathInfo.getServiceRoot().toASCIIString();
-        if (baseUri.lastIndexOf('/') == baseUri.length() - 1) {
-          baseUri = baseUri.substring(0, baseUri.length() - 1);
-        }
-        for (PathSegment precedingPS : batchRequestPathInfo.getPrecedingSegments()) {
-          baseUri = baseUri + "/" + precedingPS.getPath();
-        }
-        return baseUri;
-      }
-    } else {
-      throw new BatchException(BatchException.INVALID_PATHINFO);
-    }
-    return null;
-  }
-
-  private String trimQuota(String boundary) {
-    if (boundary.matches("\".*\"")) {
-      boundary = boundary.replace("\"", "");
-    }
-    boundary = boundary.replaceAll("\\)", "\\\\)");
-    boundary = boundary.replaceAll("\\(", "\\\\(");
-    boundary = boundary.replaceAll("\\?", "\\\\?");
-    boundary = boundary.replaceAll("\\+", "\\\\+");
-    return boundary;
-  }
-
-  private List<String> getAcceptHeader(final Map<String, List<String>> headers) {
-    List<String> acceptHeaders = new ArrayList<String>();
-    List<String> requestAcceptHeaderList = headers.get(HttpHeaders.ACCEPT.toLowerCase(Locale.ENGLISH));
-
-    if (requestAcceptHeaderList != null) {
-      acceptHeaders = requestAcceptHeaderList;
-    }
-    return acceptHeaders;
-  }
-
-  private List<Locale> getAcceptLanguageHeader(final Map<String, List<String>> headers) {
-    List<String> requestAcceptLanguageList = headers.get(HttpHeaders.ACCEPT_LANGUAGE.toLowerCase(Locale.ENGLISH));
-    List<Locale> acceptLanguages = new ArrayList<Locale>();
-    if (requestAcceptLanguageList != null) {
-      for (String acceptLanguage : requestAcceptLanguageList) {
-        String[] part = acceptLanguage.split("-");
-        String language = part[0];
-        String country = "";
-        if (part.length == 2) {
-          country = part[part.length - 1];
-        }
-        Locale locale = new Locale(language, country);
-        acceptLanguages.add(locale);
-      }
-    }
-    return acceptLanguages;
-  }
-
-  private String getContentTypeHeader(final Map<String, List<String>> headers) {
-    List<String> requestContentTypeList = headers.get(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ENGLISH));
-    String contentType = null;
-    if (requestContentTypeList != null) {
-      for (String requestContentType : requestContentTypeList) {
-        contentType = contentType != null ? contentType + "," + requestContentType : requestContentType;
-      }
-    }
-    return contentType;
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseParser.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseParser.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseParser.java
deleted file mode 100644
index 239311b..0000000
--- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseParser.java
+++ /dev/null
@@ -1,356 +0,0 @@
-/*******************************************************************************
- * 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.odata2.core.batch;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Scanner;
-import java.util.regex.MatchResult;
-import java.util.regex.Pattern;
-
-import org.apache.olingo.odata2.api.batch.BatchException;
-import org.apache.olingo.odata2.api.client.batch.BatchSingleResponse;
-import org.apache.olingo.odata2.api.commons.HttpContentType;
-import org.apache.olingo.odata2.api.commons.HttpHeaders;
-import org.apache.olingo.odata2.core.exception.ODataRuntimeException;
-
-public class BatchResponseParser {
-
-  private static final String CRLF = "\r\n";
-  private static final String REG_EX_OPTIONAL_WHITESPACE = "\\s?";
-  private static final String REG_EX_ZERO_OR_MORE_WHITESPACES = "\\s*";
-  private static final String ANY_CHARACTERS = ".*";
-
-  private static final Pattern REG_EX_BLANK_LINE = Pattern.compile("(|" + REG_EX_ZERO_OR_MORE_WHITESPACES + ")");
-  private static final Pattern REG_EX_HEADER = Pattern.compile("([a-zA-Z\\-]+):" + REG_EX_OPTIONAL_WHITESPACE + "(.*)"
-      + REG_EX_ZERO_OR_MORE_WHITESPACES);
-  private static final Pattern REG_EX_VERSION = Pattern.compile("(?:HTTP/[0-9]\\.[0-9])");
-  private static final Pattern REG_EX_ANY_BOUNDARY_STRING = Pattern.compile("--" + ANY_CHARACTERS
-      + REG_EX_ZERO_OR_MORE_WHITESPACES);
-  private static final Pattern REG_EX_STATUS_LINE = Pattern.compile(REG_EX_VERSION + "\\s" + "([0-9]{3})\\s([\\S ]+)"
-      + REG_EX_ZERO_OR_MORE_WHITESPACES);
-  private static final Pattern REG_EX_BOUNDARY_PARAMETER = Pattern.compile(REG_EX_OPTIONAL_WHITESPACE
-      + "boundary=(\".*\"|.*)" + REG_EX_ZERO_OR_MORE_WHITESPACES);
-  private static final Pattern REG_EX_CONTENT_TYPE = Pattern.compile(REG_EX_OPTIONAL_WHITESPACE
-      + HttpContentType.MULTIPART_MIXED);
-
-  private static final String REG_EX_BOUNDARY =
-      "([a-zA-Z0-9_\\-\\.'\\+]{1,70})|\"([a-zA-Z0-9_\\-\\.'\\+ \\(\\)" +
-          ",/:=\\?]{1,69}[a-zA-Z0-9_\\-\\.'\\+\\(\\),/:=\\?])\""; // See RFC 2046
-
-  private String contentTypeMime;
-  private String boundary;
-  private String currentContentId;
-  private int currentLineNumber = 0;
-
-  public BatchResponseParser(final String contentType) {
-    contentTypeMime = contentType;
-  }
-
-  public List<BatchSingleResponse> parse(final InputStream in) throws BatchException {
-    Scanner scanner = new Scanner(in, BatchHelper.DEFAULT_ENCODING);
-    scanner.useDelimiter(CRLF);
-    List<BatchSingleResponse> responseList;
-    try {
-      responseList = Collections.unmodifiableList(parseBatchResponse(scanner));
-    } finally {// NOPMD (suppress DoNotThrowExceptionInFinally)
-      scanner.close();
-      try {
-        in.close();
-      } catch (IOException e) {
-        throw new ODataRuntimeException(e);
-      }
-    }
-    return responseList;
-  }
-
-  private List<BatchSingleResponse> parseBatchResponse(final Scanner scanner) throws BatchException {
-    List<BatchSingleResponse> responses = new ArrayList<BatchSingleResponse>();
-    if (contentTypeMime != null) {
-      boundary = getBoundary(contentTypeMime);
-      parsePreamble(scanner);
-      final String closeDelimiter = "--" + boundary + "--" + REG_EX_ZERO_OR_MORE_WHITESPACES;
-      while (scanner.hasNext() && !scanner.hasNext(closeDelimiter)) {
-        responses.addAll(parseMultipart(scanner, boundary, false));
-      }
-      if (scanner.hasNext(closeDelimiter)) {
-        scanner.next(closeDelimiter);
-        currentLineNumber++;
-      } else {
-        throw new BatchException(BatchException.MISSING_CLOSE_DELIMITER.addContent(currentLineNumber));
-      }
-    } else {
-      throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
-    }
-    return responses;
-
-  }
-
-  // The method parses additional information prior to the first boundary delimiter line
-  private void parsePreamble(final Scanner scanner) {
-    while (scanner.hasNext() && !scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
-      scanner.next();
-      currentLineNumber++;
-    }
-  }
-
-  private List<BatchSingleResponse> parseMultipart(final Scanner scanner, final String boundary,
-      final boolean isChangeSet) throws BatchException {
-    Map<String, String> mimeHeaders = new HashMap<String, String>();
-    List<BatchSingleResponse> responses = new ArrayList<BatchSingleResponse>();
-    if (scanner.hasNext("--" + boundary + REG_EX_ZERO_OR_MORE_WHITESPACES)) {
-      scanner.next();
-      currentLineNumber++;
-      mimeHeaders = parseMimeHeaders(scanner);
-      currentContentId = mimeHeaders.get(BatchHelper.HTTP_CONTENT_ID.toLowerCase(Locale.ENGLISH));
-
-      final String contentType = mimeHeaders.get(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ENGLISH));
-      if (contentType == null) {
-        throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
-      }
-      if (isChangeSet) {
-        if (HttpContentType.APPLICATION_HTTP.equalsIgnoreCase(contentType)) {
-          validateEncoding(mimeHeaders.get(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING.toLowerCase(Locale.ENGLISH)));
-          parseNewLine(scanner);// mandatory
-          BatchSingleResponseImpl response = parseResponse(scanner, isChangeSet);
-          responses.add(response);
-        } else {
-          throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.APPLICATION_HTTP));
-        }
-      } else {
-        if (HttpContentType.APPLICATION_HTTP.equalsIgnoreCase(contentType)) {
-          validateEncoding(mimeHeaders.get(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING.toLowerCase(Locale.ENGLISH)));
-          parseNewLine(scanner);// mandatory
-          BatchSingleResponseImpl response = parseResponse(scanner, isChangeSet);
-          responses.add(response);
-        } else if (contentType.matches(REG_EX_OPTIONAL_WHITESPACE + HttpContentType.MULTIPART_MIXED + ANY_CHARACTERS)) {
-          String changeSetBoundary = getBoundary(contentType);
-          if (boundary.equals(changeSetBoundary)) {
-            throw new BatchException(BatchException.INVALID_CHANGESET_BOUNDARY.addContent(currentLineNumber));
-          }
-          parseNewLine(scanner);// mandatory
-          Pattern changeSetCloseDelimiter =
-              Pattern.compile("--" + changeSetBoundary + "--" + REG_EX_ZERO_OR_MORE_WHITESPACES);
-          while (!scanner.hasNext(changeSetCloseDelimiter)) {
-            responses.addAll(parseMultipart(scanner, changeSetBoundary, true));
-          }
-          scanner.next(changeSetCloseDelimiter);
-          currentLineNumber++;
-          parseOptionalEmptyLine(scanner);
-        } else {
-          throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED
-              + " or " + HttpContentType.APPLICATION_HTTP));
-        }
-      }
-    } else if (scanner.hasNext(boundary + REG_EX_ZERO_OR_MORE_WHITESPACES)) {
-      currentLineNumber++;
-      throw new BatchException(BatchException.INVALID_BOUNDARY_DELIMITER.addContent(currentLineNumber));
-    } else if (scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
-      currentLineNumber++;
-      throw new BatchException(BatchException.NO_MATCH_WITH_BOUNDARY_STRING.addContent(boundary).addContent(
-          currentLineNumber));
-    } else {
-      currentLineNumber++;
-      throw new BatchException(BatchException.MISSING_BOUNDARY_DELIMITER.addContent(currentLineNumber));
-    }
-    return responses;
-
-  }
-
-  private BatchSingleResponseImpl parseResponse(final Scanner scanner, final boolean isChangeSet)
-      throws BatchException {
-    BatchSingleResponseImpl response = new BatchSingleResponseImpl();
-    if (scanner.hasNext(REG_EX_STATUS_LINE)) {
-      scanner.next(REG_EX_STATUS_LINE);
-      currentLineNumber++;
-      final String statusCode;
-      final String statusInfo;
-      MatchResult result = scanner.match();
-      if (result.groupCount() == 2) {
-        statusCode = result.group(1);
-        statusInfo = result.group(2);
-      } else {
-        currentLineNumber++;
-        throw new BatchException(BatchException.INVALID_STATUS_LINE.addContent(scanner.next()).addContent(
-            currentLineNumber));
-      }
-
-      Map<String, String> headers = parseResponseHeaders(scanner);
-      parseNewLine(scanner);
-      String body = parseBody(scanner);
-      String contentLengthHeader = getHeaderValue(headers, HttpHeaders.CONTENT_LENGTH);
-      if (contentLengthHeader != null) {
-        int contentLength = Integer.parseInt(contentLengthHeader);
-        if (contentLength < body.length()) {
-          body = body.substring(0, contentLength);
-        }
-      }
-      response.setStatusCode(statusCode);
-      response.setStatusInfo(statusInfo);
-      response.setHeaders(headers);
-      response.setContentId(currentContentId);
-      response.setBody(body);
-    } else {
-      currentLineNumber++;
-      throw new BatchException(BatchException.INVALID_STATUS_LINE.addContent(scanner.next()).addContent(
-          currentLineNumber));
-    }
-    return response;
-  }
-
-  private void validateEncoding(final String encoding) throws BatchException {
-    if (!BatchHelper.BINARY_ENCODING.equalsIgnoreCase(encoding)) {
-      throw new BatchException(BatchException.INVALID_CONTENT_TRANSFER_ENCODING);
-    }
-  }
-
-  private Map<String, String> parseMimeHeaders(final Scanner scanner) throws BatchException {
-    Map<String, String> headers = new HashMap<String, String>();
-    while (scanner.hasNext() && !(scanner.hasNext(REG_EX_BLANK_LINE))) {
-      if (scanner.hasNext(REG_EX_HEADER)) {
-        scanner.next(REG_EX_HEADER);
-        currentLineNumber++;
-        MatchResult result = scanner.match();
-        if (result.groupCount() == 2) {
-          String headerName = result.group(1).trim().toLowerCase(Locale.ENGLISH);
-          String headerValue = result.group(2).trim();
-          headers.put(headerName, headerValue);
-        }
-      } else {
-        throw new BatchException(BatchException.INVALID_HEADER.addContent(scanner.next()));
-      }
-    }
-    return headers;
-  }
-
-  private Map<String, String> parseResponseHeaders(final Scanner scanner) throws BatchException {
-    Map<String, String> headers = new HashMap<String, String>();
-    while (scanner.hasNext() && !scanner.hasNext(REG_EX_BLANK_LINE)) {
-      if (scanner.hasNext(REG_EX_HEADER)) {
-        scanner.next(REG_EX_HEADER);
-        currentLineNumber++;
-        MatchResult result = scanner.match();
-        if (result.groupCount() == 2) {
-          String headerName = result.group(1).trim();
-          String headerValue = result.group(2).trim();
-          if (BatchHelper.HTTP_CONTENT_ID.equalsIgnoreCase(headerName)) {
-            if (currentContentId == null) {
-              currentContentId = headerValue;
-            }
-          } else {
-            headers.put(headerName, headerValue);
-          }
-        }
-      } else {
-        currentLineNumber++;
-        throw new BatchException(BatchException.INVALID_HEADER.addContent(scanner.next())
-            .addContent(currentLineNumber));
-      }
-    }
-    return headers;
-  }
-
-  private String getHeaderValue(final Map<String, String> headers, final String headerName) {
-    for (Map.Entry<String, String> header : headers.entrySet()) {
-      if (headerName.equalsIgnoreCase(header.getKey())) {
-        return header.getValue();
-      }
-    }
-    return null;
-  }
-
-  private String parseBody(final Scanner scanner) {
-    StringBuilder body = null;
-    while (scanner.hasNext() && !scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
-      if (body == null) {
-        body = new StringBuilder(scanner.next());
-      } else {
-        body.append(CRLF).append(scanner.next());
-      }
-      currentLineNumber++;
-    }
-    String responseBody = body != null ? body.toString() : null;
-    return responseBody;
-  }
-
-  private String getBoundary(final String contentType) throws BatchException {
-    Scanner contentTypeScanner = new Scanner(contentType);
-    contentTypeScanner.useDelimiter(";\\s?");
-    if (contentTypeScanner.hasNext(REG_EX_CONTENT_TYPE)) {
-      contentTypeScanner.next(REG_EX_CONTENT_TYPE);
-    } else {
-      contentTypeScanner.close();
-      throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED));
-    }
-    if (contentTypeScanner.hasNext(REG_EX_BOUNDARY_PARAMETER)) {
-      contentTypeScanner.next(REG_EX_BOUNDARY_PARAMETER);
-      MatchResult result = contentTypeScanner.match();
-      contentTypeScanner.close();
-      if (result.groupCount() == 1 && result.group(1).trim().matches(REG_EX_BOUNDARY)) {
-        return trimQuota(result.group(1).trim());
-      } else {
-        throw new BatchException(BatchException.INVALID_BOUNDARY);
-      }
-    } else {
-      contentTypeScanner.close();
-      throw new BatchException(BatchException.MISSING_PARAMETER_IN_CONTENT_TYPE);
-    }
-  }
-
-  private void parseNewLine(final Scanner scanner) throws BatchException {
-    if (scanner.hasNext() && scanner.hasNext(REG_EX_BLANK_LINE)) {
-      scanner.next();
-      currentLineNumber++;
-    } else {
-      currentLineNumber++;
-      if (scanner.hasNext()) {
-        throw new BatchException(BatchException.MISSING_BLANK_LINE.addContent(scanner.next()).addContent(
-            currentLineNumber));
-      } else {
-        throw new BatchException(BatchException.TRUNCATED_BODY.addContent(currentLineNumber));
-
-      }
-    }
-  }
-
-  private void parseOptionalEmptyLine(final Scanner scanner) {
-    if (scanner.hasNext() && scanner.hasNext(REG_EX_BLANK_LINE)) {
-      scanner.next();
-      currentLineNumber++;
-    }
-  }
-
-  private String trimQuota(String boundary) {
-    if (boundary.matches("\".*\"")) {
-      boundary = boundary.replace("\"", "");
-    }
-    boundary = boundary.replaceAll("\\)", "\\\\)");
-    boundary = boundary.replaceAll("\\(", "\\\\(");
-    boundary = boundary.replaceAll("\\?", "\\\\?");
-    boundary = boundary.replaceAll("\\+", "\\\\+");
-    return boundary;
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchBodyPart.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchBodyPart.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchBodyPart.java
new file mode 100644
index 0000000..e355f84
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchBodyPart.java
@@ -0,0 +1,155 @@
+/*******************************************************************************
+ * 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.odata2.core.batch.v2;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.commons.HttpContentType;
+import org.apache.olingo.odata2.api.commons.HttpHeaders;
+import org.apache.olingo.odata2.core.batch.v2.BatchParserCommon.HeaderField;
+import org.apache.olingo.odata2.core.exception.ODataRuntimeException;
+
+public class BatchBodyPart implements BatchPart {
+  final private Map<String, HeaderField> headers;
+  final private String boundary;
+  final private boolean isChangeSet;
+  final private boolean isStrict;
+  final private List<String> body;
+  private boolean isParsed = false;
+  private List<BatchQueryOperation> requests;
+
+  public BatchBodyPart(final List<String> bodyPartMessage, final String boundary, final boolean isStrict)
+      throws BatchException {
+    this.boundary = boundary;
+    this.isStrict = isStrict;
+
+    List<String> remainingMessage = new LinkedList<String>();
+    remainingMessage.addAll(bodyPartMessage);
+
+    headers = BatchParserCommon.consumeHeaders(remainingMessage);
+    BatchParserCommon.consumeBlankLine(remainingMessage, isStrict);
+    isChangeSet = isChangeSet(headers);
+    body = remainingMessage;
+  }
+
+  public BatchBodyPart parse(final int contentLength) throws BatchException {
+    List<String> remainingMessage = BatchParserCommon.trimStringListToLength(body, contentLength);
+    requests = consumeRequest(remainingMessage);
+    isParsed = true;
+
+    return this;
+  }
+
+  private boolean isChangeSet(final Map<String, HeaderField> headers) throws BatchException {
+    final HeaderField contentTypeField = headers.get(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ENGLISH));
+    boolean isChangeSet = false;
+
+    if (contentTypeField == null || contentTypeField.getValues().size() == 0) {
+      throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
+    }
+
+    for (String contentType : contentTypeField.getValues()) {
+      if (isContentTypeMultiPartMixed(contentType)) {
+        isChangeSet = true;
+      }
+    }
+
+    return isChangeSet;
+  }
+
+  private boolean isContentTypeMultiPartMixed(final String contentType) {
+    return contentType.contains(HttpContentType.MULTIPART_MIXED);
+  }
+
+  private List<BatchQueryOperation> consumeRequest(final List<String> remainingMessage) throws BatchException {
+    if (isChangeSet) {
+      return consumeChangeSet(remainingMessage);
+    } else {
+      return consumeQueryOperation(remainingMessage);
+    }
+  }
+
+  private List<BatchQueryOperation> consumeChangeSet(final List<String> remainingMessage)
+      throws BatchException {
+    final List<List<String>> changeRequests = splitChangeSet(remainingMessage);
+    final List<BatchQueryOperation> requestList = new LinkedList<BatchQueryOperation>();
+
+    for (List<String> changeRequest : changeRequests) {
+      requestList.add(new BatchChangeSet(changeRequest, isStrict).parse());
+    }
+
+    return requestList;
+  }
+
+  private List<List<String>> splitChangeSet(final List<String> remainingMessage)
+      throws BatchException {
+
+    final String changeSetBoundary = BatchParserCommon.getBoundary(getContentType());
+    validateChangeSetBoundary(changeSetBoundary);
+
+    return BatchParserCommon.splitMessageByBoundary(remainingMessage, changeSetBoundary);
+  }
+
+  private List<BatchQueryOperation> consumeQueryOperation(final List<String> remainingMessage)
+      throws BatchException {
+    final List<BatchQueryOperation> requestList = new LinkedList<BatchQueryOperation>();
+    requestList.add(new BatchQueryOperation(remainingMessage, isStrict).parse());
+
+    return requestList;
+  }
+
+  private void validateChangeSetBoundary(final String changeSetBoundary) throws BatchException {
+    if (changeSetBoundary.equals(boundary)) {
+      throw new BatchException(BatchException.INVALID_BOUNDARY);
+    }
+  }
+
+  private String getContentType() {
+    HeaderField contentTypeField = headers.get(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ENGLISH));
+
+    return (contentTypeField != null && contentTypeField.getValues().size() > 0) ? contentTypeField.getValues().get(0)
+        : "";
+  }
+
+  @Override
+  public Map<String, HeaderField> getHeaders() {
+    return headers;
+  }
+
+  @Override
+  public boolean isStrict() {
+    return isStrict;
+  }
+
+  public boolean isChangeSet() {
+    return isChangeSet;
+  }
+
+  public List<BatchQueryOperation> getRequests() {
+    if (!isParsed) {
+      throw new ODataRuntimeException("Batch part must be parsed first");
+    }
+
+    return requests;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchChangeSet.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchChangeSet.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchChangeSet.java
new file mode 100644
index 0000000..5331ff8
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchChangeSet.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * 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.odata2.core.batch.v2;
+
+import java.util.List;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+
+public class BatchChangeSet extends BatchQueryOperation {
+  private BatchQueryOperation request;
+
+  public BatchChangeSet(final List<String> message, final boolean isStrict) throws BatchException {
+    super(message, isStrict);
+  }
+
+  @Override
+  public BatchChangeSet parse() throws BatchException {
+    headers = BatchParserCommon.consumeHeaders(message);
+    BatchParserCommon.consumeBlankLine(message, isStrict);
+
+    request = new BatchQueryOperation(message, isStrict).parse();
+
+    return this;
+  }
+
+  public BatchQueryOperation getRequest() {
+    return request;
+  }
+
+  @Override
+  public List<String> getBody() {
+    return request.getBody();
+  }
+
+  @Override
+  public String getHttpMethod() {
+    return request.getHttpMethod();
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParser.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParser.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParser.java
new file mode 100644
index 0000000..b64453b
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParser.java
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * 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.odata2.core.batch.v2;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.batch.BatchParserResult;
+import org.apache.olingo.odata2.api.batch.BatchRequestPart;
+import org.apache.olingo.odata2.api.client.batch.BatchSingleResponse;
+import org.apache.olingo.odata2.api.ep.EntityProviderBatchProperties;
+import org.apache.olingo.odata2.api.uri.PathInfo;
+import org.apache.olingo.odata2.api.uri.PathSegment;
+import org.apache.olingo.odata2.core.exception.ODataRuntimeException;
+
+public class BatchParser {
+
+  private final PathInfo batchRequestPathInfo;
+  private final String contentTypeMime;
+  private final boolean isStrict;
+
+  public BatchParser(final String contentType, final boolean isStrict) {
+    this(contentType, null, isStrict);
+  }
+
+  public BatchParser(final String contentType, final EntityProviderBatchProperties properties, final boolean isStrict) {
+    contentTypeMime = contentType;
+    batchRequestPathInfo = (properties != null) ? properties.getPathInfo() : null;
+    this.isStrict = isStrict;
+  }
+
+  @SuppressWarnings("unchecked")
+  public List<BatchSingleResponse> parseBatchResponse(final InputStream in) throws BatchException {
+    return (List<BatchSingleResponse>) parse(in, new BatchResponseTransformator());
+  }
+
+  @SuppressWarnings("unchecked")
+  public List<BatchRequestPart> parseBatchRequest(final InputStream in) throws BatchException {
+    return (List<BatchRequestPart>) parse(in, new BatchRequestTransformator());
+  }
+
+  private List<? extends BatchParserResult> parse(final InputStream in, final BatchTransformator transformator)
+      throws BatchException {
+    try {
+      return parseBatch(in, transformator);
+    } catch (IOException e) {
+      throw new ODataRuntimeException(e);
+    } finally {
+      try {
+        in.close();
+      } catch (IOException e) {
+        throw new ODataRuntimeException(e);
+      }
+    }
+  }
+
+  private List<BatchParserResult> parseBatch(final InputStream in,
+      final BatchTransformator transformator) throws BatchException, IOException {
+
+    final String baseUri = getBaseUri();
+    final String boundary = BatchParserCommon.getBoundary(contentTypeMime);
+    final List<BatchParserResult> resultList = new LinkedList<BatchParserResult>();
+    final List<List<String>> bodyPartStrings = splitBodyParts(in, boundary);
+
+    for (List<String> bodyPartString : bodyPartStrings) {
+      BatchBodyPart bodyPart = new BatchBodyPart(bodyPartString, boundary, isStrict);
+      resultList.addAll(transformator.transform(bodyPart, batchRequestPathInfo, baseUri));
+    }
+
+    return resultList;
+  }
+
+  private List<List<String>> splitBodyParts(final InputStream in, final String boundary)
+      throws IOException, BatchException {
+
+    final BufferedReaderIncludingLineEndings reader = new BufferedReaderIncludingLineEndings(new InputStreamReader(in));
+    final List<String> message = reader.toList();
+    reader.close();
+
+    return BatchParserCommon.splitMessageByBoundary(message, boundary);
+  }
+
+  private String getBaseUri() throws BatchException {
+    String baseUri = "";
+
+    if (batchRequestPathInfo != null && batchRequestPathInfo.getServiceRoot() != null) {
+      final String uri = batchRequestPathInfo.getServiceRoot().toASCIIString();
+
+      baseUri = addPathSegements(removeLastSlash(uri));
+    }
+
+    return baseUri;
+  }
+
+  private String addPathSegements(String baseUri) {
+    for (PathSegment precedingPS : batchRequestPathInfo.getPrecedingSegments()) {
+      baseUri = baseUri + "/" + precedingPS.getPath();
+    }
+
+    return baseUri;
+  }
+
+  private String removeLastSlash(String baseUri) {
+    if (baseUri.lastIndexOf('/') == baseUri.length() - 1) {
+      baseUri = baseUri.substring(0, baseUri.length() - 1);
+    }
+
+    return baseUri;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParserCommon.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParserCommon.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParserCommon.java
new file mode 100644
index 0000000..51314dd
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParserCommon.java
@@ -0,0 +1,414 @@
+/*******************************************************************************
+ * 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.odata2.core.batch.v2;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.commons.HttpContentType;
+import org.apache.olingo.odata2.api.commons.HttpHeaders;
+import org.apache.olingo.odata2.api.uri.PathInfo;
+import org.apache.olingo.odata2.api.uri.PathSegment;
+import org.apache.olingo.odata2.core.ODataPathSegmentImpl;
+import org.apache.olingo.odata2.core.PathInfoImpl;
+import org.apache.olingo.odata2.core.batch.AcceptParser;
+import org.apache.olingo.odata2.core.commons.Decoder;
+
+public class BatchParserCommon {
+  private static final String BOUNDARY_IDENTIFIER = "boundary=";
+  private static final String REG_EX_BOUNDARY =
+      "([a-zA-Z0-9_\\-\\.'\\+]{1,70})|\"([a-zA-Z0-9_\\-\\.'\\+\\s\\" +
+          "(\\),/:=\\?]{1,69}[a-zA-Z0-9_\\-\\.'\\+\\(\\),/:=\\?])\""; // See RFC 2046
+
+  private static final Pattern REG_EX_HEADER = Pattern.compile("([a-zA-Z\\-]+):\\s?(.*)\\s*");
+
+  public static List<String> trimStringListToLength(final List<String> list, final int length) {
+    final Iterator<String> iter = list.iterator();
+    final List<String> result = new ArrayList<String>();
+    boolean isEndReached = false;
+    int currentLength = 0;
+
+    while (!isEndReached && iter.hasNext()) {
+      String currentLine = iter.next();
+
+      if (currentLength + currentLine.length() <= length) {
+        result.add(currentLine);
+        currentLength += currentLine.length();
+      } else {
+        result.add(currentLine.substring(0, length - currentLength));
+        isEndReached = true;
+      }
+    }
+
+    return result;
+  }
+
+  public static String stringListToString(final List<String> list) {
+    StringBuilder builder = new StringBuilder();
+
+    for (String currentLine : list) {
+      builder.append(currentLine);
+    }
+
+    return builder.toString();
+  }
+
+  public static InputStream convertMessageToInputStream(final List<String> message, final int contentLength)
+      throws BatchException {
+    List<String> shortenedMessage = BatchParserCommon.trimStringListToLength(message, contentLength);
+
+    return new ByteArrayInputStream(BatchParserCommon.stringListToString(shortenedMessage).getBytes());
+  }
+
+  static List<List<String>> splitMessageByBoundary(final List<String> message, final String boundary)
+      throws BatchException {
+    final List<List<String>> messageParts = new LinkedList<List<String>>();
+    List<String> currentPart = new ArrayList<String>();
+    boolean isEndReached = false;
+
+    for (String currentLine : message) {
+      if (currentLine.contains("--" + boundary + "--")) {
+        removeEndingCRLFFromList(currentPart);
+        messageParts.add(currentPart);
+        isEndReached = true;
+      } else if (currentLine.contains("--" + boundary)) {
+        removeEndingCRLFFromList(currentPart);
+        messageParts.add(currentPart);
+        currentPart = new LinkedList<String>();
+      } else {
+        currentPart.add(currentLine);
+      }
+
+      if (isEndReached) {
+        break;
+      }
+    }
+
+    // Remove preamble
+    if (messageParts.size() > 0) {
+      messageParts.remove(0);
+    } else {
+      throw new BatchException(BatchException.MISSING_BOUNDARY_DELIMITER);
+    }
+
+    if (messageParts.size() == 0) {
+      throw new BatchException(BatchException.NO_MATCH_WITH_BOUNDARY_STRING);
+    }
+
+    if (!isEndReached) {
+      throw new BatchException(BatchException.MISSING_CLOSE_DELIMITER);
+    }
+
+    return messageParts;
+  }
+
+  private static void removeEndingCRLFFromList(final List<String> list) {
+    if (list.size() > 0) {
+      String lastLine = list.remove(list.size() - 1);
+      list.add(removeEndingCRLF(lastLine));
+    }
+  }
+
+  public static String removeEndingCRLF(final String line) {
+    Pattern pattern = Pattern.compile("(.*)(\r\n){1}( *)", Pattern.DOTALL);
+    Matcher matcher = pattern.matcher(line);
+
+    if (matcher.matches()) {
+      return matcher.group(1);
+    } else {
+      return line;
+    }
+  }
+
+  static Map<String, HeaderField> consumeHeaders(final List<String> remainingMessage) throws BatchException {
+    final Map<String, HeaderField> headers = new HashMap<String, HeaderField>();
+    boolean isHeader = true;
+    String currentLine;
+    Iterator<String> iter = remainingMessage.iterator();
+
+    while (iter.hasNext() && isHeader) {
+      currentLine = iter.next();
+      Matcher headerMatcher = REG_EX_HEADER.matcher(currentLine);
+
+      if (headerMatcher.matches() && headerMatcher.groupCount() == 2) {
+        iter.remove();
+
+        String headerName = headerMatcher.group(1).trim();
+        String headerNameLowerCase = headerName.toLowerCase(Locale.ENGLISH);
+        String headerValue = headerMatcher.group(2).trim();
+
+        if (HttpHeaders.ACCEPT.equalsIgnoreCase(headerNameLowerCase)) {
+          List<String> acceptHeaders = AcceptParser.parseAcceptHeaders(headerValue);
+          headers.put(headerNameLowerCase, new HeaderField(headerName, acceptHeaders));
+        } else if (HttpHeaders.ACCEPT_LANGUAGE.equalsIgnoreCase(headerNameLowerCase)) {
+          List<String> acceptLanguageHeaders = AcceptParser.parseAcceptableLanguages(headerValue);
+          headers.put(headerNameLowerCase, new HeaderField(headerName, acceptLanguageHeaders));
+        } else {
+          HeaderField headerField = headers.get(headerNameLowerCase);
+          headerField = headerField == null ? new HeaderField(headerName) : headerField;
+          headers.put(headerNameLowerCase, headerField);
+          headerField.getValues().add(headerValue);
+        }
+      } else {
+        isHeader = false;
+      }
+    }
+
+    return Collections.unmodifiableMap(headers);
+  }
+
+  static void consumeBlankLine(final List<String> remainingMessage, final boolean isStrict) throws BatchException {
+    if (remainingMessage.size() > 0 && "".equals(remainingMessage.get(0).trim())) {
+      remainingMessage.remove(0);
+    } else {
+      if (isStrict) {
+        throw new BatchException(BatchException.MISSING_BLANK_LINE);
+      }
+    }
+  }
+
+  static void consumeLastBlankLine(final List<String> message, final boolean isStrict) throws BatchException {
+    if (message.size() > 0 && "".equals(message.get(message.size() - 1).trim())) {
+      message.remove(message.size() - 1);
+    } else {
+      if (isStrict) {
+        throw new BatchException(BatchException.MISSING_BLANK_LINE);
+      }
+    }
+  }
+
+  static String getBoundary(final String contentType) throws BatchException {
+    if (contentType.contains(HttpContentType.MULTIPART_MIXED)) {
+      String[] parts = contentType.split(BOUNDARY_IDENTIFIER);
+
+      if (parts.length == 2) {
+        if (parts[1].matches(REG_EX_BOUNDARY)) {
+          return trimQuota(parts[1].trim());
+        } else {
+          throw new BatchException(BatchException.INVALID_BOUNDARY);
+        }
+      } else {
+        throw new BatchException(BatchException.MISSING_PARAMETER_IN_CONTENT_TYPE);
+      }
+    } else {
+      throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED));
+    }
+  }
+
+  static Map<String, List<String>> parseQueryParameter(final String httpRequest) {
+    Map<String, List<String>> queryParameter = new HashMap<String, List<String>>();
+
+    String[] requestParts = httpRequest.split(" ");
+    if (requestParts.length == 3) {
+
+      String[] parts = requestParts[1].split("\\?");
+      if (parts.length == 2) {
+        String[] parameters = parts[1].split("&");
+
+        for (String parameter : parameters) {
+          String[] parameterParts = parameter.split("=");
+          String parameterName = parameterParts[0].toLowerCase(Locale.ENGLISH);
+
+          if (parameterParts.length == 2) {
+            List<String> valueList = queryParameter.get(parameterName);
+            valueList = valueList == null ? new LinkedList<String>() : valueList;
+            queryParameter.put(parameterName, valueList);
+
+            String[] valueParts = parameterParts[1].split(",");
+            for (String value : valueParts) {
+              valueList.add(Decoder.decode(value));
+            }
+          }
+        }
+      }
+    }
+
+    return queryParameter;
+  }
+
+  public static PathInfo parseRequestUri(final String httpRequest, final PathInfo batchRequestPathInfo,
+      final String baseUri)
+      throws BatchException {
+
+    final String odataPathSegmentsAsString;
+    final String queryParametersAsString;
+
+    PathInfoImpl pathInfo = new PathInfoImpl();
+    pathInfo.setServiceRoot(batchRequestPathInfo.getServiceRoot());
+    pathInfo.setPrecedingPathSegment(batchRequestPathInfo.getPrecedingSegments());
+
+    String[] requestParts = httpRequest.split(" ");
+    if (requestParts.length == 3) {
+      String uri = requestParts[1];
+      Pattern regexRequestUri;
+
+      try {
+        URI uriObject = new URI(uri);
+        if (uriObject.isAbsolute()) {
+          regexRequestUri = Pattern.compile(baseUri + "/([^/][^?]*)(\\?.*)?");
+        } else {
+          regexRequestUri = Pattern.compile("([^/][^?]*)(\\?.*)?");
+
+        }
+
+        Matcher uriParts = regexRequestUri.matcher(uri);
+
+        if (uriParts.lookingAt() && uriParts.groupCount() == 2) {
+          odataPathSegmentsAsString = uriParts.group(1);
+          queryParametersAsString = uriParts.group(2) != null ? uriParts.group(2) : "";
+
+          pathInfo.setODataPathSegment(parseODataPathSegments(odataPathSegmentsAsString));
+          if (!odataPathSegmentsAsString.startsWith("$")) {
+            String requestUri = baseUri + "/" + odataPathSegmentsAsString + queryParametersAsString;
+            pathInfo.setRequestUri(new URI(requestUri));
+          }
+
+        } else {
+          throw new BatchException(BatchException.INVALID_URI);
+        }
+
+      } catch (URISyntaxException e) {
+        throw new BatchException(BatchException.INVALID_URI, e);
+      }
+    } else {
+      throw new BatchException(BatchException.INVALID_REQUEST_LINE);
+    }
+
+    return pathInfo;
+  }
+
+  public static List<PathSegment> parseODataPathSegments(final String odataPathSegmentsAsString) {
+    final List<PathSegment> odataPathSegments = new ArrayList<PathSegment>();
+    final String[] pathParts = odataPathSegmentsAsString.split("/");
+
+    for (final String pathSegment : pathParts) {
+      odataPathSegments.add(new ODataPathSegmentImpl(pathSegment, null));
+    }
+
+    return odataPathSegments;
+  }
+
+  private static String trimQuota(String boundary) {
+    if (boundary.matches("\".*\"")) {
+      boundary = boundary.replace("\"", "");
+    }
+
+    return boundary;
+  }
+
+  public static Map<String, String> headerFieldMapToSingleMap(final Map<String, HeaderField> headers) {
+    final Map<String, String> singleMap = new HashMap<String, String>();
+
+    for (final String key : headers.keySet()) {
+      HeaderField field = headers.get(key);
+      String value = field.getValues().size() > 0 ? field.getValues().get(0) : "";
+      singleMap.put(field.getFieldName(), value);
+    }
+
+    return singleMap;
+  }
+
+  public static Map<String, List<String>> headerFieldMapToMultiMap(final Map<String, HeaderField> headers) {
+    final Map<String, List<String>> singleMap = new HashMap<String, List<String>>();
+
+    for (final String key : headers.keySet()) {
+      HeaderField field = headers.get(key);
+      singleMap.put(field.getFieldName(), field.getValues());
+    }
+
+    return singleMap;
+  }
+
+  public static class HeaderField implements Cloneable {
+    private String fieldName;
+    private List<String> values;
+
+    public HeaderField(final String fieldName) {
+      this(fieldName, new ArrayList<String>());
+    }
+
+    public HeaderField(final String fieldName, final List<String> values) {
+      this.fieldName = fieldName;
+      this.values = values;
+    }
+
+    public String getFieldName() {
+      return fieldName;
+    }
+
+    public List<String> getValues() {
+      return values;
+    }
+
+    public void setValues(final List<String> values) {
+      this.values = values;
+    }
+
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((fieldName == null) ? 0 : fieldName.hashCode());
+      return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+      if (this == obj) {
+        return true;
+      }
+      if (obj == null) {
+        return false;
+      }
+      if (getClass() != obj.getClass()) {
+        return false;
+      }
+      HeaderField other = (HeaderField) obj;
+      if (fieldName == null) {
+        if (other.fieldName != null) {
+          return false;
+        }
+      } else if (!fieldName.equals(other.fieldName)) {
+        return false;
+      }
+      return true;
+    }
+
+    @Override
+    public HeaderField clone() {
+      List<String> newValues = new ArrayList<String>();
+      newValues.addAll(values);
+
+      return new HeaderField(fieldName, newValues);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchPart.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchPart.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchPart.java
new file mode 100644
index 0000000..258f48a
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchPart.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * 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.odata2.core.batch.v2;
+
+import java.util.Map;
+
+import org.apache.olingo.odata2.core.batch.v2.BatchParserCommon.HeaderField;
+
+public interface BatchPart {
+  public Map<String, HeaderField> getHeaders();
+
+  public boolean isStrict();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchQueryOperation.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchQueryOperation.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchQueryOperation.java
new file mode 100644
index 0000000..179fffb
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchQueryOperation.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * 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.odata2.core.batch.v2;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.core.batch.v2.BatchParserCommon.HeaderField;
+
+public class BatchQueryOperation implements BatchPart {
+
+  protected final boolean isStrict;
+  protected String httpMethod;
+  protected Map<String, HeaderField> headers;
+  protected List<String> body;
+  protected int bodySize;
+  protected List<String> message;
+
+  public BatchQueryOperation(final List<String> message, final boolean isStrict) {
+    this.isStrict = isStrict;
+    this.message = message;
+  }
+
+  public BatchQueryOperation parse() throws BatchException {
+    httpMethod = consumeHttpMethod(message);
+    headers = BatchParserCommon.consumeHeaders(message);
+    BatchParserCommon.consumeBlankLine(message, isStrict);
+    body = message;
+
+    return this;
+  }
+
+  protected String consumeHttpMethod(final List<String> message) throws BatchException {
+    if (message.size() > 0 && !message.get(0).trim().equals("")) {
+      String method = message.get(0);
+      message.remove(0);
+
+      return method;
+    } else {
+      throw new BatchException(BatchException.INVALID_QUERY_OPERATION_METHOD);
+    }
+  }
+
+  public String getHttpMethod() {
+    return httpMethod;
+  }
+
+  public List<String> getBody() {
+    return body;
+  }
+
+  public int getBodySize() {
+    return bodySize;
+  }
+
+  @Override
+  public Map<String, HeaderField> getHeaders() {
+    return headers;
+  }
+
+  @Override
+  public boolean isStrict() {
+    return isStrict;
+  }
+}