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:16:18 UTC

[05/13] [OLINGO-129] Merge with current master

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/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/a6e2fbe5/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..d60c29a
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchBodyPart.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * 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 org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.commons.HttpHeaders;
+import org.apache.olingo.odata2.core.batch.v2.BufferedReaderIncludingLineEndings.Line;
+import org.apache.olingo.odata2.core.batch.v2.Header.HeaderField;
+
+public class BatchBodyPart implements BatchPart {
+  final private String boundary;
+  final private boolean isStrict;
+  final List<Line> remainingMessage = new LinkedList<Line>();
+
+  private Header headers;
+  private boolean isChangeSet;
+  private List<BatchQueryOperation> requests;
+
+  public BatchBodyPart(final List<Line> bodyPartMessage, final String boundary, final boolean isStrict)
+      throws BatchException {
+    this.boundary = boundary;
+    this.isStrict = isStrict;
+
+    remainingMessage.addAll(bodyPartMessage);
+  }
+
+  public BatchBodyPart parse() throws BatchException {
+    headers = BatchParserCommon.consumeHeaders(remainingMessage);
+    BatchParserCommon.consumeBlankLine(remainingMessage, isStrict);
+    isChangeSet = isChangeSet(headers);
+    requests = consumeRequest(remainingMessage);
+
+    return this;
+  }
+
+  private boolean isChangeSet(final Header headers) throws BatchException {
+    final List<String> contentTypes = headers.getHeaders(HttpHeaders.CONTENT_TYPE);
+    boolean isChangeSet = false;
+
+    if (contentTypes.size() == 0) {
+      throw new BatchException(BatchException.MISSING_CONTENT_TYPE.addContent(headers.getLineNumber()));
+    }
+
+    for (String contentType : contentTypes) {
+      if (isContentTypeMultiPartMixed(contentType)) {
+        isChangeSet = true;
+      }
+    }
+
+    return isChangeSet;
+  }
+
+  private boolean isContentTypeMultiPartMixed(final String contentType) {
+    return BatchParserCommon.PATTERN_MULTIPART_BOUNDARY.matcher(contentType).matches();
+  }
+
+  private List<BatchQueryOperation> consumeRequest(final List<Line> remainingMessage) throws BatchException {
+    if (isChangeSet) {
+      return consumeChangeSet(remainingMessage);
+    } else {
+      return consumeQueryOperation(remainingMessage);
+    }
+  }
+
+  private List<BatchQueryOperation> consumeChangeSet(final List<Line> remainingMessage)
+      throws BatchException {
+    final List<List<Line>> changeRequests = splitChangeSet(remainingMessage);
+    final List<BatchQueryOperation> requestList = new LinkedList<BatchQueryOperation>();
+
+    for (List<Line> changeRequest : changeRequests) {
+      requestList.add(new BatchChangeSetPart(changeRequest, isStrict).parse());
+    }
+
+    return requestList;
+  }
+
+  private List<List<Line>> splitChangeSet(final List<Line> remainingMessage)
+      throws BatchException {
+
+    final HeaderField contentTypeField = headers.getHeaderField(HttpHeaders.CONTENT_TYPE);
+    final String changeSetBoundary =
+        BatchParserCommon.getBoundary(contentTypeField.getValueNotNull(), contentTypeField.getLineNumber());
+    validateChangeSetBoundary(changeSetBoundary, headers);
+
+    return BatchParserCommon.splitMessageByBoundary(remainingMessage, changeSetBoundary);
+  }
+
+  private List<BatchQueryOperation> consumeQueryOperation(final List<Line> 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, final Header header) throws BatchException {
+    if (changeSetBoundary.equals(boundary)) {
+      throw new BatchException(BatchException.INVALID_BOUNDARY.addContent(header.getHeaderField(
+          HttpHeaders.CONTENT_TYPE).getLineNumber()));
+    }
+  }
+
+  @Override
+  public Header getHeaders() {
+    return headers;
+  }
+
+  @Override
+  public boolean isStrict() {
+    return isStrict;
+  }
+
+  public boolean isChangeSet() {
+    return isChangeSet;
+  }
+
+  public List<BatchQueryOperation> getRequests() {
+    return requests;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchChangeSetPart.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchChangeSetPart.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchChangeSetPart.java
new file mode 100644
index 0000000..f3b0699
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchChangeSetPart.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * 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;
+import org.apache.olingo.odata2.core.batch.v2.BufferedReaderIncludingLineEndings.Line;
+
+public class BatchChangeSetPart extends BatchQueryOperation {
+  private BatchQueryOperation request;
+
+  public BatchChangeSetPart(final List<Line> message, final boolean isStrict) throws BatchException {
+    super(message, isStrict);
+  }
+
+  @Override
+  public BatchChangeSetPart 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<Line> getBody() {
+    return request.getBody();
+  }
+
+  @Override
+  public Line getHttpStatusLine() {
+    return request.getHttpStatusLine();
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/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..09bf987
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParser.java
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * 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.batch.v2.BufferedReaderIncludingLineEndings.Line;
+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, 1);
+    final List<BatchParserResult> resultList = new LinkedList<BatchParserResult>();
+    final List<List<Line>> bodyPartStrings = splitBodyParts(in, boundary);
+
+    for (List<Line> bodyPartString : bodyPartStrings) {
+      BatchBodyPart bodyPart = new BatchBodyPart(bodyPartString, boundary, isStrict).parse();
+      resultList.addAll(transformator.transform(bodyPart, batchRequestPathInfo, baseUri));
+    }
+
+    return resultList;
+  }
+  
+  private List<List<Line>> splitBodyParts(final InputStream in, final String boundary)
+      throws IOException, BatchException {
+
+    final BufferedReaderIncludingLineEndings reader = new BufferedReaderIncludingLineEndings(new InputStreamReader(in));
+    final List<Line> 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/a6e2fbe5/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..c2751ee
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParserCommon.java
@@ -0,0 +1,259 @@
+/*******************************************************************************
+ * 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.util.ArrayList;
+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.core.batch.AcceptParser;
+import org.apache.olingo.odata2.core.batch.v2.BufferedReaderIncludingLineEndings.Line;
+import org.apache.olingo.odata2.core.commons.Decoder;
+
+public class BatchParserCommon {
+
+  private static final Pattern PATTERN_LAST_CRLF = Pattern.compile("(.*)(\r\n){1}( *)", Pattern.DOTALL);
+
+  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 String REX_EX_MULTIPART_BOUNDARY = "multipart/mixed;\\s*boundary=(.+)";
+  private static final String REG_EX_APPLICATION_HTTP = "application/http";
+  public static final Pattern PATTERN_MULTIPART_BOUNDARY = Pattern.compile(REX_EX_MULTIPART_BOUNDARY,
+      Pattern.CASE_INSENSITIVE);
+  public static final Pattern PATTERN_HEADER_LINE = Pattern.compile("([a-zA-Z\\-]+):\\s?(.*)\\s*");
+  public static final Pattern PATTERN_CONTENT_TYPE_APPLICATION_HTTP = Pattern.compile(REG_EX_APPLICATION_HTTP,
+      Pattern.CASE_INSENSITIVE);
+  public static final Pattern PATTERN_RELATIVE_URI = Pattern.compile("([^/][^?]*)(\\?.*)?");
+
+  public static String trimLineListToLength(final List<Line> list, final int length) {
+    final String message = lineListToString(list);
+    final int lastIndex = Math.min(length, message.length());
+
+    return (lastIndex > 0) ? message.substring(0, lastIndex) : "";
+  }
+
+  public static String lineListToString(final List<Line> list) {
+    StringBuilder builder = new StringBuilder();
+
+    for (Line currentLine : list) {
+      builder.append(currentLine.toString());
+    }
+
+    return builder.toString();
+  }
+
+  public static InputStream convertLineListToInputStream(final List<Line> messageList, final int contentLength)
+      throws BatchException {
+    final String message = trimLineListToLength(messageList, contentLength);
+
+    return new ByteArrayInputStream(message.getBytes());
+  }
+
+  public static InputStream convertLineListToInputStream(final List<Line> messageList)
+      throws BatchException {
+    final String message = lineListToString(messageList);
+
+    return new ByteArrayInputStream(message.getBytes());
+  }
+
+  static List<List<Line>> splitMessageByBoundary(final List<Line> message, final String boundary)
+      throws BatchException {
+    final List<List<Line>> messageParts = new LinkedList<List<Line>>();
+    List<Line> currentPart = new ArrayList<Line>();
+    boolean isEndReached = false;
+
+    final String quotedBoundary = Pattern.quote(boundary);
+    final Pattern boundaryDelimiterPattern = Pattern.compile("--" + quotedBoundary + "--[\\s ]*");
+    final Pattern boundaryPattern = Pattern.compile("--" + quotedBoundary + "[\\s ]*");
+
+    for (Line currentLine : message) {
+      if (boundaryDelimiterPattern.matcher(currentLine.toString()).matches()) {
+        removeEndingCRLFFromList(currentPart);
+        messageParts.add(currentPart);
+        isEndReached = true;
+      } else if (boundaryPattern.matcher(currentLine.toString()).matches()) {
+        removeEndingCRLFFromList(currentPart);
+        messageParts.add(currentPart);
+        currentPart = new LinkedList<Line>();
+      } else {
+        currentPart.add(currentLine);
+      }
+
+      if (isEndReached) {
+        break;
+      }
+    }
+
+    final int lineNumer = (message.size() > 0) ? message.get(0).getLineNumber() : 0;
+    // Remove preamble
+    if (messageParts.size() > 0) {
+      messageParts.remove(0);
+    } else {
+
+      throw new BatchException(BatchException.MISSING_BOUNDARY_DELIMITER.addContent(lineNumer));
+    }
+
+    if (!isEndReached) {
+      throw new BatchException(BatchException.MISSING_CLOSE_DELIMITER.addContent(lineNumer));
+    }
+
+    if (messageParts.size() == 0) {
+      throw new BatchException(BatchException.NO_MATCH_WITH_BOUNDARY_STRING.addContent(boundary).addContent(lineNumer));
+    }
+
+    return messageParts;
+  }
+
+  private static void removeEndingCRLFFromList(final List<Line> list) {
+    if (list.size() > 0) {
+      Line lastLine = list.remove(list.size() - 1);
+      list.add(removeEndingCRLF(lastLine));
+    }
+  }
+
+  public static Line removeEndingCRLF(final Line line) {
+    Pattern pattern = PATTERN_LAST_CRLF;
+    Matcher matcher = pattern.matcher(line.toString());
+
+    if (matcher.matches()) {
+      return new Line(matcher.group(1), line.getLineNumber());
+    } else {
+      return line;
+    }
+  }
+
+  public static Header consumeHeaders(final List<Line> remainingMessage) throws BatchException {
+    final int headerLineNumber = remainingMessage.size() != 0 ? remainingMessage.get(0).getLineNumber() : 0;
+    final Header headers = new Header(headerLineNumber);
+    final Iterator<Line> iter = remainingMessage.iterator();
+    final AcceptParser acceptParser = new AcceptParser();
+    Line currentLine;
+    int acceptLineNumber = 0;
+    int acceptLanguageLineNumber = 0;
+    boolean isHeader = true;
+
+    while (iter.hasNext() && isHeader) {
+      currentLine = iter.next();
+      final Matcher headerMatcher = PATTERN_HEADER_LINE.matcher(currentLine.toString());
+
+      if (headerMatcher.matches() && headerMatcher.groupCount() == 2) {
+        iter.remove();
+
+        String headerName = headerMatcher.group(1).trim();
+        String headerValue = headerMatcher.group(2).trim();
+
+        if (HttpHeaders.ACCEPT.equalsIgnoreCase(headerName)) {
+          acceptParser.addAcceptHeaderValue(headerValue);
+          acceptLineNumber = currentLine.getLineNumber();
+        } else if (HttpHeaders.ACCEPT_LANGUAGE.equalsIgnoreCase(headerName)) {
+          acceptParser.addAcceptLanguageHeaderValue(headerValue);
+          acceptLanguageLineNumber = currentLine.getLineNumber();
+        } else {
+          headers.addHeader(headerName, Header.splitValuesByComma(headerValue), currentLine.getLineNumber());
+        }
+      } else {
+        isHeader = false;
+      }
+    }
+
+    headers.addHeader(HttpHeaders.ACCEPT, acceptParser.parseAcceptHeaders(), acceptLineNumber);
+    headers.addHeader(HttpHeaders.ACCEPT_LANGUAGE, acceptParser.parseAcceptableLanguages(), acceptLanguageLineNumber);
+
+    return headers;
+  }
+
+  public static void consumeBlankLine(final List<Line> remainingMessage, final boolean isStrict)
+      throws BatchException {
+    if (remainingMessage.size() > 0 && remainingMessage.get(0).toString().matches("\\s*\r\n\\s*")) {
+      remainingMessage.remove(0);
+    } else {
+      if (isStrict) {
+        final int lineNumber = (remainingMessage.size() > 0) ? remainingMessage.get(0).getLineNumber() : 0;
+        throw new BatchException(BatchException.MISSING_BLANK_LINE.addContent("[None]").addContent(lineNumber));
+      }
+    }
+  }
+
+  public static String getBoundary(final String contentType, final int line) throws BatchException {
+    final Matcher boundaryMatcher = PATTERN_MULTIPART_BOUNDARY.matcher(contentType);
+
+    if (boundaryMatcher.matches()) {
+      final String boundary = boundaryMatcher.group(1);
+      if (boundary.matches(REG_EX_BOUNDARY)) {
+        return trimQuota(boundary);
+      } else {
+        throw new BatchException(BatchException.INVALID_BOUNDARY.addContent(line));
+      }
+    } else {
+      throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED));
+    }
+  }
+
+  private static String trimQuota(String boundary) {
+    if (boundary.matches("\".*\"")) {
+      boundary = boundary.replace("\"", "");
+    }
+
+    return boundary;
+  }
+  
+  public static Map<String, List<String>> parseQueryParameter(final Line httpRequest) {
+    Map<String, List<String>> queryParameter = new HashMap<String, List<String>>();
+
+    String[] requestParts = httpRequest.toString().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;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/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..69f211f
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchPart.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * 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;
+
+public interface BatchPart {
+  public Header getHeaders();
+
+  public boolean isStrict();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/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..9bbd019
--- /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 org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.core.batch.v2.BufferedReaderIncludingLineEndings.Line;
+
+public class BatchQueryOperation implements BatchPart {
+
+  protected final boolean isStrict;
+  protected Line httpStatusLine;
+  protected Header headers;
+  protected List<Line> body;
+  protected int bodySize;
+  protected List<Line> message;
+
+  public BatchQueryOperation(final List<Line> message, final boolean isStrict) {
+    this.isStrict = isStrict;
+    this.message = message;
+  }
+
+  public BatchQueryOperation parse() throws BatchException {
+    httpStatusLine = consumeHttpStatusLine(message);
+    headers = BatchParserCommon.consumeHeaders(message);
+    BatchParserCommon.consumeBlankLine(message, isStrict);
+    body = message;
+
+    return this;
+  }
+
+  protected Line consumeHttpStatusLine(final List<Line> message) throws BatchException {
+    if (message.size() > 0 && !message.get(0).toString().trim().equals("")) {
+      final Line method = message.get(0);
+      message.remove(0);
+
+      return method;
+    } else {
+      final int line = (message.size() > 0) ? message.get(0).getLineNumber() : 0;
+      throw new BatchException(BatchException.MISSING_METHOD.addContent(line));
+    }
+  }
+
+  public Line getHttpStatusLine() {
+    return httpStatusLine;
+  }
+
+  public List<Line> getBody() {
+    return body;
+  }
+
+  public int getBodySize() {
+    return bodySize;
+  }
+
+  @Override
+  public Header getHeaders() {
+    return headers;
+  }
+
+  @Override
+  public boolean isStrict() {
+    return isStrict;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchRequestTransformator.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchRequestTransformator.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchRequestTransformator.java
new file mode 100644
index 0000000..66600b5
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchRequestTransformator.java
@@ -0,0 +1,175 @@
+/*******************************************************************************
+ * 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.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.batch.BatchParserResult;
+import org.apache.olingo.odata2.api.commons.HttpHeaders;
+import org.apache.olingo.odata2.api.commons.ODataHttpMethod;
+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.core.batch.BatchHelper;
+import org.apache.olingo.odata2.core.batch.BatchRequestPartImpl;
+import org.apache.olingo.odata2.core.batch.v2.BatchTransformatorCommon.HttpRequestStatusLine;
+import org.apache.olingo.odata2.core.batch.v2.Header.HeaderField;
+
+public class BatchRequestTransformator implements BatchTransformator {
+
+  @Override
+  public List<BatchParserResult> transform(final BatchBodyPart bodyPart, final PathInfo pathInfo, final String baseUri)
+      throws BatchException {
+
+    final List<ODataRequest> requests = new LinkedList<ODataRequest>();
+    final List<BatchParserResult> resultList = new ArrayList<BatchParserResult>();
+
+    validateHeader(bodyPart, false);
+
+    for (BatchQueryOperation queryOperation : bodyPart.getRequests()) {
+      requests.add(processQueryOperation(bodyPart, pathInfo, baseUri, queryOperation));
+    }
+
+    resultList.add(new BatchRequestPartImpl(bodyPart.isChangeSet(), requests));
+    return resultList;
+  }
+
+  private void validateHeader(final BatchPart bodyPart, final boolean isChangeSet) throws BatchException {
+    Header headers = bodyPart.getHeaders();
+
+    BatchTransformatorCommon.validateContentType(headers);
+    BatchTransformatorCommon.validateContentTransferEncoding(headers, isChangeSet);
+  }
+
+  private ODataRequest processQueryOperation(final BatchBodyPart bodyPart, final PathInfo pathInfo,
+      final String baseUri, final BatchQueryOperation queryOperation) throws BatchException {
+
+    if (bodyPart.isChangeSet()) {
+      BatchQueryOperation encapsulatedQueryOperation = ((BatchChangeSetPart) queryOperation).getRequest();
+      Header headers = transformHeader(encapsulatedQueryOperation, queryOperation);
+      validateHeader(queryOperation, true);
+
+      return createRequest(queryOperation, headers, pathInfo, baseUri, bodyPart.isChangeSet());
+    } else {
+
+      Header headers = transformHeader(queryOperation, bodyPart);
+      return createRequest(queryOperation, headers, pathInfo, baseUri, bodyPart.isChangeSet());
+    }
+  }
+
+  private ODataRequest createRequest(final BatchQueryOperation operation, final Header headers,
+      final PathInfo pathInfo, final String baseUri, final boolean isChangeSet) throws BatchException {
+
+    final HttpRequestStatusLine statusLine = new HttpRequestStatusLine( operation.getHttpStatusLine(), 
+                                                                        baseUri, 
+                                                                        pathInfo);
+    statusLine.validateHttpMethod(isChangeSet);
+
+    validateBody(statusLine, operation);
+    InputStream bodyStrean = getBodyStream(operation, headers, statusLine);
+
+    ODataRequestBuilder requestBuilder = ODataRequest.method(statusLine.getMethod())
+        .acceptableLanguages(getAcceptLanguageHeaders(headers))
+        .acceptHeaders(headers.getHeaders(HttpHeaders.ACCEPT))
+        .allQueryParameters(BatchParserCommon.parseQueryParameter(operation.getHttpStatusLine()))
+        .body(bodyStrean)
+        .requestHeaders(headers.toMultiMap())
+        .pathInfo(statusLine.getPathInfo());
+
+    final String contentType = headers.getHeader(HttpHeaders.CONTENT_TYPE);
+    if (contentType != null) {
+      requestBuilder.contentType(contentType);
+    }
+
+    return requestBuilder.build();
+  }
+
+  private void validateBody(final HttpRequestStatusLine httpStatusLine, final BatchQueryOperation operation)
+      throws BatchException {
+    if (httpStatusLine.getMethod().equals(ODataHttpMethod.GET) && isUnvalidGetRequestBody(operation)) {
+      throw new BatchException(BatchException.INVALID_REQUEST_LINE
+          .addContent(httpStatusLine.getMethod())
+          .addContent(httpStatusLine.getLineNumber()));
+    }
+  }
+
+  private boolean isUnvalidGetRequestBody(final BatchQueryOperation operation) {
+    return (operation.getBody().size() > 1)
+        || (operation.getBody().size() == 1 && !"".equals(operation.getBody().get(0).toString().trim()));
+  }
+
+  private InputStream getBodyStream(final BatchQueryOperation operation, final Header headers,
+      final HttpRequestStatusLine httpStatusLine) throws BatchException {
+
+    if (httpStatusLine.getMethod().equals(ODataHttpMethod.GET)) {
+      return new ByteArrayInputStream(new byte[0]);
+    } else {
+      int contentLength = BatchTransformatorCommon.getContentLength(headers);
+
+      if (contentLength == -1) {
+        return BatchParserCommon.convertLineListToInputStream(operation.getBody());
+      } else {
+        return BatchParserCommon.convertLineListToInputStream(operation.getBody(), contentLength);
+      }
+    }
+  }
+
+  private Header transformHeader(final BatchPart operation, final BatchPart parentPart) {
+    final Header headers = operation.getHeaders().clone();
+    headers.removeHeader(BatchHelper.HTTP_CONTENT_ID);
+    final HeaderField operationHeader = operation.getHeaders().getHeaderField(BatchHelper.HTTP_CONTENT_ID);
+    final HeaderField parentHeader = parentPart.getHeaders().getHeaderField(BatchHelper.HTTP_CONTENT_ID);
+
+    if (operationHeader != null && operationHeader.getValues().size() != 0) {
+      headers.addHeader(BatchHelper.REQUEST_HEADER_CONTENT_ID, operationHeader.getValues(), operationHeader
+          .getLineNumber());
+    }
+
+    if (parentHeader != null && parentHeader.getValues().size() != 0) {
+      headers.addHeader(BatchHelper.MIME_HEADER_CONTENT_ID, parentHeader.getValues(), parentHeader.getLineNumber());
+    }
+
+    return headers;
+  }
+
+  private List<Locale> getAcceptLanguageHeaders(final Header headers) {
+    final List<String> acceptLanguageValues = headers.getHeaders(HttpHeaders.ACCEPT_LANGUAGE);
+    List<Locale> acceptLanguages = new ArrayList<Locale>();
+
+    for (String acceptLanguage : acceptLanguageValues) {
+      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;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchResponseTransformator.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchResponseTransformator.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchResponseTransformator.java
new file mode 100644
index 0000000..5e4198c
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchResponseTransformator.java
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * 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.ArrayList;
+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.client.batch.BatchSingleResponse;
+import org.apache.olingo.odata2.api.uri.PathInfo;
+import org.apache.olingo.odata2.core.batch.BatchHelper;
+import org.apache.olingo.odata2.core.batch.BatchSingleResponseImpl;
+import org.apache.olingo.odata2.core.batch.v2.BatchTransformatorCommon.HttpResponsetStatusLine;
+
+public class BatchResponseTransformator implements BatchTransformator {
+
+  public BatchResponseTransformator() {}
+
+  @Override
+  public List<BatchParserResult> transform(final BatchBodyPart bodyPart, final PathInfo pathInfo, final String baseUri)
+      throws BatchException {
+    return processQueryOperation(bodyPart, pathInfo, baseUri);
+  }
+
+  private List<BatchParserResult> processQueryOperation(final BatchBodyPart bodyPart,
+      final PathInfo pathInfo,
+      final String baseUri) throws BatchException {
+
+    List<BatchParserResult> resultList = new ArrayList<BatchParserResult>();
+
+    BatchTransformatorCommon.validateContentType(bodyPart.getHeaders());
+    resultList.addAll(handleBodyPart(bodyPart));
+
+    return resultList;
+  }
+
+  private List<BatchParserResult> handleBodyPart(final BatchBodyPart bodyPart) throws BatchException {
+    List<BatchParserResult> bodyPartResult = new ArrayList<BatchParserResult>();
+
+    if (bodyPart.isChangeSet()) {
+      for (BatchQueryOperation operation : bodyPart.getRequests()) {
+        bodyPartResult.add(transformChangeSet((BatchChangeSetPart) operation));
+      }
+    } else {
+      final String contentId = bodyPart.getHeaders().getHeader(BatchHelper.HTTP_CONTENT_ID);
+
+      bodyPartResult.add(transformQueryOperation(bodyPart.getRequests().get(0), contentId));
+    }
+
+    return bodyPartResult;
+  }
+
+  private BatchSingleResponse transformChangeSet(final BatchChangeSetPart changeSet) throws BatchException {
+    BatchTransformatorCommon.validateContentTransferEncoding(changeSet.getHeaders(), true);
+    final String contentId = changeSet.getHeaders().getHeader(BatchHelper.HTTP_CONTENT_ID);
+
+    return transformQueryOperation(changeSet.getRequest(), contentId);
+  }
+
+  private BatchSingleResponse transformQueryOperation(final BatchQueryOperation operation, final String contentId)
+      throws BatchException {
+
+    final HttpResponsetStatusLine statusLine = new HttpResponsetStatusLine(operation.getHttpStatusLine());
+
+    BatchSingleResponseImpl response = new BatchSingleResponseImpl();
+    response.setContentId(contentId);
+    response.setHeaders(operation.getHeaders().toSingleMap());
+    response.setStatusCode(statusLine.getStatusCode());
+    response.setStatusInfo(statusLine.getStatusInfo());
+    response.setBody(getBody(operation));
+
+    return response;
+  }
+
+  private String getBody(final BatchQueryOperation operation) throws BatchException {
+    int contentLength = BatchTransformatorCommon.getContentLength(operation.getHeaders());
+
+    if (contentLength == -1) {
+      return BatchParserCommon.lineListToString(operation.getBody());
+    } else {
+      return BatchParserCommon.trimLineListToLength(operation.getBody(), contentLength);
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchTransformator.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchTransformator.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchTransformator.java
new file mode 100644
index 0000000..5dcddbf
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchTransformator.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * 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;
+import org.apache.olingo.odata2.api.batch.BatchParserResult;
+import org.apache.olingo.odata2.api.uri.PathInfo;
+
+public interface BatchTransformator {
+  public List<BatchParserResult> transform(BatchBodyPart bodyPart, PathInfo pathInfo, String baseUri)
+      throws BatchException;
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchTransformatorCommon.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchTransformatorCommon.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchTransformatorCommon.java
new file mode 100644
index 0000000..c07c06f
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchTransformatorCommon.java
@@ -0,0 +1,276 @@
+/*******************************************************************************
+ * 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.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+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.commons.ODataHttpMethod;
+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.BatchHelper;
+import org.apache.olingo.odata2.core.batch.v2.BufferedReaderIncludingLineEndings.Line;
+import org.apache.olingo.odata2.core.batch.v2.Header.HeaderField;
+
+public class BatchTransformatorCommon {
+  public static void validateContentType(final Header headers) throws BatchException {
+    List<String> contentTypes = headers.getHeaders(HttpHeaders.CONTENT_TYPE);
+
+    if (contentTypes.size() == 0) {
+      throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
+    }
+    if (!headers.isHeaderMatching(HttpHeaders.CONTENT_TYPE, BatchParserCommon.PATTERN_MULTIPART_BOUNDARY)
+      & !headers.isHeaderMatching(HttpHeaders.CONTENT_TYPE, BatchParserCommon.PATTERN_CONTENT_TYPE_APPLICATION_HTTP)) {
+      throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(
+          HttpContentType.MULTIPART_MIXED + " or " + HttpContentType.APPLICATION_HTTP));
+    }
+  }
+
+  public static void validateContentTransferEncoding(final Header headers, final boolean isChangeRequest)
+      throws BatchException {
+    final HeaderField contentTransferField = headers.getHeaderField(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING);
+
+    if (contentTransferField != null) {
+      final List<String> contentTransferValues = contentTransferField.getValues();
+      if (contentTransferValues.size() == 1) {
+        String encoding = contentTransferValues.get(0);
+
+        if (!BatchHelper.BINARY_ENCODING.equalsIgnoreCase(encoding)) {
+          throw new BatchException(
+              BatchException.INVALID_CONTENT_TRANSFER_ENCODING.addContent(contentTransferField.getLineNumber()));
+        }
+      } else {
+        throw new BatchException(BatchException.INVALID_HEADER.addContent(contentTransferField.getLineNumber()));
+      }
+    } else {
+      if (isChangeRequest) {
+        throw new BatchException(BatchException.INVALID_CONTENT_TRANSFER_ENCODING.addContent(headers.getLineNumber()));
+      }
+    }
+  }
+
+  public static int getContentLength(final Header headers) throws BatchException {
+    final HeaderField contentLengthField = headers.getHeaderField(HttpHeaders.CONTENT_LENGTH);
+
+    if (contentLengthField != null && contentLengthField.getValues().size() == 1) {
+      final List<String> contentLengthValues = contentLengthField.getValues();
+
+      try {
+        int contentLength = Integer.parseInt(contentLengthValues.get(0));
+
+        if (contentLength < 0) {
+          throw new BatchException(BatchException.INVALID_HEADER.addContent(contentLengthField.getValue()).addContent(
+              contentLengthField.getLineNumber()));
+        }
+
+        return contentLength;
+      } catch (NumberFormatException e) {
+        throw new BatchException(BatchException.INVALID_HEADER.addContent(contentLengthField.getValue()).addContent(
+            contentLengthField.getLineNumber()), e);
+      }
+    }
+
+    return -1;
+  }
+  
+  public static class HttpResponsetStatusLine {
+    private static final String REG_EX_STATUS_LINE = "(?:HTTP/[0-9]\\.[0-9])\\s([0-9]{3})\\s([\\S ]+)\\s*";
+    private Line httpStatusLine;
+    private String statusCode;
+    private String statusInfo;
+
+    public HttpResponsetStatusLine(final Line httpStatusLine) throws BatchException {
+      this.httpStatusLine = httpStatusLine;
+      parse();
+    }
+
+    private void parse() throws BatchException {
+      final Pattern regexPattern = Pattern.compile(REG_EX_STATUS_LINE);
+      final Matcher matcher = regexPattern.matcher(httpStatusLine.toString());
+
+      if (matcher.find()) {
+        statusCode = matcher.group(1);
+        statusInfo = matcher.group(2);
+      } else {
+        throw new BatchException(BatchException.INVALID_STATUS_LINE.addContent(httpStatusLine.toString())
+            .addContent(httpStatusLine.getLineNumber()));
+      }
+    }
+
+    public String getStatusCode() {
+      return statusCode;
+    }
+
+    public String getStatusInfo() {
+      return statusInfo;
+    }
+  }
+
+  public static class HttpRequestStatusLine {
+    private static final Set<String> HTTP_BATCH_METHODS = new HashSet<String>(Arrays.asList(new String[] { "GET" }));
+    private static final Set<String> HTTP_CHANGE_SET_METHODS = new HashSet<String>(Arrays.asList(new String[] { "POST",
+        "PUT", "DELETE", "MERGE", "PATCH" }));
+    private static final String HTTP_VERSION = "HTTP/1.1";
+    
+    final private Line statusLine;
+    final String requestBaseUri;
+    final PathInfo batchRequestPathInfo;
+
+    private ODataHttpMethod method;
+    private PathInfo pathInfo;
+    private String httpVersion;
+
+    public HttpRequestStatusLine(final Line httpStatusLine, final String baseUri, final PathInfo pathInfo)
+        throws BatchException {
+      statusLine = httpStatusLine;
+      requestBaseUri = baseUri;
+      batchRequestPathInfo = pathInfo;
+
+      parse();
+    }
+
+    private void parse() throws BatchException {
+      final String[] parts = statusLine.toString().split(" ");
+
+      if (parts.length == 3) {
+        try {
+          method = parseMethod(parts[0]);
+          pathInfo = parseUri(parts[1]);
+          httpVersion = parseHttpVersion(parts[2]);
+        } catch (IllegalArgumentException e) {
+          throw new BatchException(BatchException.MISSING_METHOD.addContent(statusLine.getLineNumber()), e);
+        }
+      } else {
+        throw new BatchException(BatchException.INVALID_REQUEST_LINE.addContent(statusLine.toString())
+            .addContent(statusLine.getLineNumber()));
+      }
+    }
+
+    private ODataHttpMethod parseMethod(final String method) throws BatchException {
+      try {
+        return ODataHttpMethod.valueOf(method.trim());
+      } catch (IllegalArgumentException e) {
+        throw new BatchException(BatchException.MISSING_METHOD.addContent(statusLine.getLineNumber()), e);
+      }
+    }
+
+    private PathInfo parseUri(final String uri)
+        throws BatchException {
+      final PathInfoImpl pathInfo = new PathInfoImpl();
+      final String odataPathSegmentsAsString;
+      final String queryParametersAsString;
+      Pattern regexRequestUri;
+
+      pathInfo.setServiceRoot(batchRequestPathInfo.getServiceRoot());
+      pathInfo.setPrecedingPathSegment(batchRequestPathInfo.getPrecedingSegments());
+
+      try {
+        URI uriObject = new URI(uri);
+        if (uriObject.isAbsolute()) {
+          regexRequestUri = Pattern.compile(requestBaseUri + "/([^/][^?]*)(\\?.*)?");
+        } else {
+          regexRequestUri = BatchParserCommon.PATTERN_RELATIVE_URI;
+
+        }
+
+        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 = requestBaseUri + "/" + odataPathSegmentsAsString + queryParametersAsString;
+            pathInfo.setRequestUri(new URI(requestUri));
+          }
+
+        } else {
+          throw new BatchException(BatchException.INVALID_URI.addContent(statusLine.getLineNumber()));
+        }
+      } catch (URISyntaxException e) {
+        throw new BatchException(BatchException.INVALID_URI.addContent(statusLine.getLineNumber()), e);
+      }
+
+      return pathInfo;
+    }
+
+    private 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 String parseHttpVersion(final String httpVersion) throws BatchException {
+      if (!HTTP_VERSION.equals(httpVersion.trim())) {
+        throw new BatchException(BatchException.INVALID_REQUEST_LINE
+                                                  .addContent(statusLine.toString())
+                                                  .addContent(statusLine.getLineNumber()));
+      } else {
+        return HTTP_VERSION;
+      }
+    }
+
+    public void validateHttpMethod(boolean isChangeSet) throws BatchException {
+      Set<String> validMethods = (isChangeSet) ? HTTP_CHANGE_SET_METHODS : HTTP_BATCH_METHODS;
+      
+      if (!validMethods.contains(getMethod().toString())) {
+        if (isChangeSet) {
+          throw new BatchException(BatchException.INVALID_CHANGESET_METHOD.addContent(statusLine.getLineNumber()));
+        } else {
+          throw new BatchException(BatchException.INVALID_QUERY_OPERATION_METHOD
+              .addContent(statusLine.getLineNumber()));
+        }
+      }
+    }
+
+    public ODataHttpMethod getMethod() {
+      return method;
+    }
+
+    public PathInfo getPathInfo() {
+      return pathInfo;
+    }
+
+    public String getHttpVersion() {
+      return httpVersion;
+    }
+
+    public int getLineNumber() {
+      return statusLine.getLineNumber();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BufferedReaderIncludingLineEndings.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BufferedReaderIncludingLineEndings.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BufferedReaderIncludingLineEndings.java
new file mode 100644
index 0000000..5c7fb7c
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BufferedReaderIncludingLineEndings.java
@@ -0,0 +1,275 @@
+/*******************************************************************************
+ * 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.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BufferedReaderIncludingLineEndings extends Reader {
+  private static final char CR = '\r';
+  private static final char LF = '\n';
+  private static final int EOF = -1;
+  private static final int BUFFER_SIZE = 8192;
+  private Reader reader;
+  private char[] buffer;
+  private int offset = 0;
+  private int limit = 0;
+
+  public BufferedReaderIncludingLineEndings(final Reader reader) {
+    this(reader, BUFFER_SIZE);
+  }
+
+  public BufferedReaderIncludingLineEndings(final Reader reader, final int bufferSize) {
+    if (bufferSize <= 0) {
+      throw new IllegalArgumentException("Buffer size must be greater than zero.");
+    }
+
+    this.reader = reader;
+    buffer = new char[bufferSize];
+  }
+
+  @Override
+  public int read(final char[] charBuffer, final int bufferOffset, final int length) throws IOException {
+    if ((bufferOffset + length) > charBuffer.length) {
+      throw new IndexOutOfBoundsException("Buffer is too small");
+    }
+
+    if (length < 0 || bufferOffset < 0) {
+      throw new IndexOutOfBoundsException("Offset and length must be grater than zero");
+    }
+
+    // Check if buffer is filled. Return if EOF is reached
+    // Is buffer refill required
+    if (limit == offset || isEOF()) {
+      fillBuffer();
+
+      if (isEOF()) {
+        return EOF;
+      }
+    }
+
+    int bytesRead = 0;
+    int bytesToRead = length;
+    int currentOutputOffset = bufferOffset;
+
+    while (bytesToRead != 0) {
+      // Is buffer refill required?
+      if (limit == offset) {
+        fillBuffer();
+
+        if (isEOF()) {
+          bytesToRead = 0;
+        }
+      }
+
+      if (bytesToRead > 0) {
+        int readByte = Math.min(limit - offset, bytesToRead);
+        bytesRead += readByte;
+        bytesToRead -= readByte;
+
+        for (int i = 0; i < readByte; i++) {
+          charBuffer[currentOutputOffset++] = buffer[offset++];
+        }
+      }
+    }
+
+    return bytesRead;
+  }
+
+  public List<Line> toList() throws IOException {
+    final List<Line> result = new ArrayList<Line>();
+    String currentLine;
+    int counter = 1;
+
+    while ((currentLine = readLine()) != null) {
+      result.add(new Line(currentLine, counter++));
+    }
+
+    return result;
+  }
+
+  public String readLine() throws IOException {
+    if (limit == EOF) {
+      return null;
+    }
+
+    final StringBuilder stringBuffer = new StringBuilder();
+    boolean foundLineEnd = false; // EOF will be considered as line ending
+
+    while (!foundLineEnd) {
+     // Is buffer refill required?
+      if (limit == offset) {
+        if (fillBuffer() == EOF) {
+          foundLineEnd = true;
+        }
+      }
+
+      if (!foundLineEnd) {
+        char currentChar = buffer[offset++];
+        stringBuffer.append(currentChar);
+
+        if (currentChar == LF) {
+          foundLineEnd = true;
+        } else if (currentChar == CR) {
+          foundLineEnd = true;
+
+          // Check next char. Consume \n if available
+          // Is buffer refill required?
+          if (limit == offset) {
+            fillBuffer();
+          }
+
+          // Check if there is at least one character
+          if (limit != EOF && buffer[offset] == LF) {
+            stringBuffer.append(LF);
+            offset++;
+          }
+        }
+      }
+    }
+
+    return (stringBuffer.length() == 0) ? null : stringBuffer.toString();
+  }
+
+  @Override
+  public void close() throws IOException {
+    reader.close();
+  }
+
+  @Override
+  public boolean ready() throws IOException {
+    // Not EOF and buffer refill is not required
+    return !isEOF() && !(limit == offset);
+  }
+
+  @Override
+  public void reset() throws IOException {
+    throw new IOException("Reset is not supported");
+  }
+
+  @Override
+  public void mark(final int readAheadLimit) throws IOException {
+    throw new IOException("Mark is not supported");
+  }
+
+  @Override
+  public boolean markSupported() {
+    return false;
+  }
+
+  @Override
+  public long skip(final long n) throws IOException {
+    if (n == 0) {
+      return 0;
+    } else if (n < 0) {
+      throw new IllegalArgumentException("skip value is negative");
+    } else {
+      long charactersToSkip = n;
+      long charactersSkiped = 0;
+
+      while (charactersToSkip != 0) {
+        // Is buffer refill required?
+        if (limit == offset) {
+          fillBuffer();
+
+          if (isEOF()) {
+            charactersToSkip = 0;
+          }
+        }
+
+        // Check if more characters are available
+        if (!isEOF()) {
+          int skipChars = (int) Math.min(limit - offset, charactersToSkip);
+
+          charactersSkiped += skipChars;
+          charactersToSkip -= skipChars;
+          offset += skipChars;
+        }
+      }
+
+      return charactersSkiped;
+    }
+  }
+
+  private boolean isEOF() {
+    return limit == EOF;
+  }
+
+  private int fillBuffer() throws IOException {
+    limit = reader.read(buffer, 0, buffer.length);
+    offset = 0;
+
+    return limit;
+  }
+
+  public static class Line {
+    private final int lineNumber;
+    private final String content;
+
+    public Line(final String content, final int lineNumber) {
+      this.content = content;
+      this.lineNumber = lineNumber;
+    }
+
+    public int getLineNumber() {
+      return lineNumber;
+    }
+
+    @Override
+    public String toString() {
+      return content;
+    }
+
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((content == null) ? 0 : content.hashCode());
+      result = prime * result + lineNumber;
+      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;
+      }
+      Line other = (Line) obj;
+      if (content == null) {
+        if (other.content != null) {
+          return false;
+        }
+      } else if (!content.equals(other.content)) {
+        return false;
+      }
+      if (lineNumber != other.lineNumber) {
+        return false;
+      }
+      return true;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/Header.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/Header.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/Header.java
new file mode 100644
index 0000000..ba44b25
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/Header.java
@@ -0,0 +1,248 @@
+/*******************************************************************************
+ * 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.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+public class Header implements Cloneable {
+
+  private final Map<String, HeaderField> headers = new HashMap<String, HeaderField>();
+  private int lineNumber;
+
+  public Header(final int lineNumer) {
+    lineNumber = lineNumer;
+  }
+
+  public void addHeader(final String name, final String value, final int lineNumber) {
+    final HeaderField headerField = getHeaderFieldOrDefault(name, lineNumber);
+    final List<String> headerValues = headerField.getValues();
+
+    if (!headerValues.contains(value)) {
+      headerValues.add(value);
+    }
+  }
+
+  public void addHeader(final String name, final List<String> values, final int lineNumber) {
+    final HeaderField headerField = getHeaderFieldOrDefault(name, lineNumber);
+    final List<String> headerValues = headerField.getValues();
+
+    for (final String value : values) {
+      if (!headerValues.contains(value)) {
+        headerValues.add(value);
+      }
+    }
+  }
+
+  public boolean isHeaderMatching(final String name, final Pattern pattern) {
+    if (getHeaders(name).size() != 1) {
+      return false;
+    } else {
+      return pattern.matcher(getHeaders(name).get(0)).matches();
+    }
+  }
+
+  public void removeHeader(final String name) {
+    headers.remove(name.toLowerCase(Locale.ENGLISH));
+  }
+
+  public String getHeader(final String name) {
+    final HeaderField headerField = getHeaderField(name);
+
+    return (headerField == null) ? null : headerField.getValue();
+  }
+
+  public String getHeaderNotNull(final String name) {
+    final HeaderField headerField = getHeaderField(name);
+
+    return (headerField == null) ? "" : headerField.getValueNotNull();
+  }
+
+  public List<String> getHeaders(final String name) {
+    final HeaderField headerField = getHeaderField(name);
+
+    return (headerField == null) ? new ArrayList<String>() : headerField.getValues();
+  }
+
+  public HeaderField getHeaderField(final String name) {
+    return headers.get(name.toLowerCase(Locale.ENGLISH));
+  }
+
+  public int getLineNumber() {
+    return lineNumber;
+  }
+
+  public Map<String, String> toSingleMap() {
+    final Map<String, String> singleMap = new HashMap<String, String>();
+
+    for (final String key : headers.keySet()) {
+      HeaderField field = headers.get(key);
+      singleMap.put(field.getFieldName(), getHeader(key));
+    }
+
+    return singleMap;
+  }
+
+  public Map<String, List<String>> toMultiMap() {
+    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;
+  }
+
+  private HeaderField getHeaderFieldOrDefault(final String name, final int lineNumber) {
+    HeaderField headerField = headers.get(name.toLowerCase(Locale.ENGLISH));
+
+    if (headerField == null) {
+      headerField = new HeaderField(name, lineNumber);
+      headers.put(name.toLowerCase(Locale.ENGLISH), headerField);
+    }
+
+    return headerField;
+  }
+
+  @Override
+  public Header clone() {
+    final Header newInstance = new Header(lineNumber);
+
+    for (final String key : headers.keySet()) {
+      newInstance.headers.put(key, headers.get(key).clone());
+    }
+
+    return newInstance;
+  }
+
+  public static List<String> splitValuesByComma(final String headerValue) {
+    final List<String> singleValues = new ArrayList<String>();
+
+    String[] parts = headerValue.split(",");
+    for (final String value : parts) {
+      singleValues.add(value.trim());
+    }
+
+    return singleValues;
+  }
+
+  public static class HeaderField implements Cloneable {
+    private final String fieldName;
+    private final List<String> values;
+    private final int lineNumber;
+
+    public HeaderField(final String fieldName, final int lineNumber) {
+      this(fieldName, new ArrayList<String>(), lineNumber);
+    }
+
+    public HeaderField(final String fieldName, final List<String> values, final int lineNumber) {
+      this.fieldName = fieldName;
+      this.values = values;
+      this.lineNumber = lineNumber;
+    }
+
+    public String getFieldName() {
+      return fieldName;
+    }
+
+    public List<String> getValues() {
+      return values;
+    }
+
+    public String getValue() {
+      final StringBuilder result = new StringBuilder();
+
+      for (final String value : values) {
+        result.append(value);
+        result.append(", ");
+      }
+
+      if (result.length() > 0) {
+        result.delete(result.length() - 2, result.length());
+      }
+
+      return result.toString();
+    }
+
+    public String getValueNotNull() {
+      final String value = getValue();
+
+      return (value == null) ? "" : value;
+    }
+
+    @Override
+    public HeaderField clone() {
+      List<String> newValues = new ArrayList<String>();
+      newValues.addAll(values);
+
+      return new HeaderField(fieldName, newValues, lineNumber);
+    }
+
+    public int getLineNumber() {
+      return lineNumber;
+    }
+
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((fieldName == null) ? 0 : fieldName.hashCode());
+      result = prime * result + lineNumber;
+      result = prime * result + ((values == null) ? 0 : values.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;
+      }
+      if (lineNumber != other.lineNumber) {
+        return false;
+      }
+      if (values == null) {
+        if (other.values != null) {
+          return false;
+        }
+      } else if (!values.equals(other.values)) {
+        return false;
+      }
+      return true;
+    }
+  }
+}