olingo-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From chri...@apache.org
Subject [5/5] git commit: [OLINGO-472] BatchParser first draft
Date Mon, 03 Nov 2014 13:47:31 GMT
[OLINGO-472] BatchParser first draft

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


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

Branch: refs/heads/olingo472
Commit: 32247295fce782a393457bc7d7a08be030d434fd
Parents: 3e11738
Author: Christian Holzer <c.holzer@sap.com>
Authored: Thu Oct 16 17:39:39 2014 +0200
Committer: Christian Amend <chrisam@apache.org>
Committed: Mon Nov 3 14:46:08 2014 +0100

----------------------------------------------------------------------
 .../server/api/batch/BatchParserResult.java     |   23 +
 .../server/api/batch/BatchRequestPart.java      |   42 +
 .../server/api/batch/ODataResponsePart.java     |   42 +
 .../server/core/batch/BatchException.java       |   65 +
 .../olingo/server/core/batch/StringUtil.java    |   54 +
 .../server/core/batch/parser/BatchBodyPart.java |  135 +
 .../core/batch/parser/BatchChangeSetPart.java   |   56 +
 .../server/core/batch/parser/BatchParser.java   |   91 +
 .../core/batch/parser/BatchParserCommon.java    |  228 ++
 .../server/core/batch/parser/BatchPart.java     |   25 +
 .../core/batch/parser/BatchQueryOperation.java  |   82 +
 .../core/batch/parser/BatchRequestPartImpl.java |   47 +
 .../BufferedReaderIncludingLineEndings.java     |  286 +++
 .../olingo/server/core/batch/parser/Header.java |  181 ++
 .../server/core/batch/parser/HeaderField.java   |  121 +
 .../BatchRequestTransformator.java              |  191 ++
 .../batch/transformator/BatchTransformator.java |   29 +
 .../transformator/BatchTransformatorCommon.java |  250 ++
 .../core/batch/writer/BatchResponseWriter.java  |  228 ++
 .../batch/writer/ODataResponsePartImpl.java     |   44 +
 .../server-core-exceptions-i18n.properties      |   21 +
 .../core/batch/BatchRequestParserTest.java      | 1308 ++++++++++
 .../batch/parser/BatchParserCommonTest.java     |  230 ++
 .../BufferedReaderIncludingLineEndingsTest.java |  484 ++++
 .../server/core/batch/parser/HeaderTest.java    |  179 ++
 .../batch/writer/BatchResponseWriterTest.java   |  179 ++
 .../src/test/resources/batchLarge.batch         | 2422 ++++++++++++++++++
 .../src/test/resources/batchWithContent.batch   |    1 +
 .../src/test/resources/batchWithPost.batch      |   39 +
 29 files changed, 7083 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchParserResult.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchParserResult.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchParserResult.java
new file mode 100644
index 0000000..93bc34d
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchParserResult.java
@@ -0,0 +1,23 @@
+/*
+ * 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.server.api.batch;
+
+public interface BatchParserResult {
+
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchRequestPart.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchRequestPart.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchRequestPart.java
new file mode 100644
index 0000000..ba5319f
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchRequestPart.java
@@ -0,0 +1,42 @@
+/*
+ * 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.server.api.batch;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.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 extends BatchParserResult {
+
+  /**
+   * Get the info if a BatchPart is a ChangeSet
+   * @return true or false
+   */
+  public boolean isChangeSet();
+
+  /**
+   * Get requests. If a BatchPart is a Query Operation, the list contains one request.
+   * @return a list of {@link ODataRequest}
+   */
+  public List<ODataRequest> getRequests();
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java
new file mode 100644
index 0000000..8dd7480
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java
@@ -0,0 +1,42 @@
+/*
+ * 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.server.api.batch;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.ODataResponse;
+
+public interface ODataResponsePart {
+  /**
+   * Returns a collection of ODataResponses.
+   * Each collections contains at least one {@link ODataResponse}.
+   * 
+   * If this instance represents a change set, there are may many ODataResponses
+   *  
+   * @return a list of {@link ODataResponse}
+   */
+  public List<ODataResponse> getResponses();
+  
+  /**
+   * Returns true if the current instance represents a change set.
+   * 
+   * @return true or false
+   */
+  public boolean isChangeSet();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/BatchException.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/BatchException.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/BatchException.java
new file mode 100644
index 0000000..aafe141
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/BatchException.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch;
+
+import org.apache.olingo.server.api.ODataTranslatedException;
+
+public class BatchException extends ODataTranslatedException {
+  public static enum MessageKeys implements MessageKey {
+    INVALID_BOUNDARY,
+    INVALID_CHANGESET_METHOD,
+    INVALID_CONTENT,
+    INVALID_CONTENT_LENGTH,
+    INVALID_CONTENT_TRANSFER_ENCODING,
+    INVALID_CONTENT_TYPE,
+    INVALID_HEADER,
+    INVALID_HTTP_VERSION,
+    INVALID_METHOD,
+    INVALID_QUERY_OPERATION_METHOD,
+    INVALID_STATUS_LINE,
+    INVALID_URI,
+    MISSING_BLANK_LINE,
+    MISSING_BOUNDARY_DELIMITER,
+    MISSING_CLOSE_DELIMITER,
+    MISSING_CONTENT_ID,
+    MISSING_CONTENT_TRANSFER_ENCODING,
+    MISSING_CONTENT_TYPE,
+    MISSING_MANDATORY_HEADER, FORBIDDEN_HEADER;
+
+    @Override
+    public String getKey() {
+      return name();
+    }
+  }
+
+  private static final long serialVersionUID = -907752788975531134L;
+
+  public BatchException(final String developmentMessage, final MessageKey messageKey, final int lineNumber) {
+    this(developmentMessage, messageKey, "" + lineNumber);
+  }
+
+  public BatchException(final String developmentMessage, final MessageKey messageKey, final String... parameters) {
+    super(developmentMessage, messageKey, parameters);
+  }
+
+  @Override
+  protected String getBundleName() {
+    return DEFAULT_SERVER_BUNDLE_NAME;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/StringUtil.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/StringUtil.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/StringUtil.java
new file mode 100644
index 0000000..2602852
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/StringUtil.java
@@ -0,0 +1,54 @@
+/*
+ * 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.server.core.batch;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+
+import org.apache.olingo.commons.api.ODataRuntimeException;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings;
+
+public class StringUtil {
+  
+  
+  public static String toString(final InputStream in) throws IOException {
+    final StringBuilder builder = new StringBuilder();
+    final BufferedReaderIncludingLineEndings reader = new BufferedReaderIncludingLineEndings(new InputStreamReader(in));
+    String currentLine;
+    
+    while((currentLine = reader.readLine()) != null) {
+      builder.append(currentLine);
+    }
+    
+    reader.close();
+    
+    return builder.toString();
+  }
+
+  public static InputStream toInputStream(final String string) {
+    try {
+      return new ByteArrayInputStream(string.getBytes("UTF-8"));
+    } catch (UnsupportedEncodingException e) {
+      throw new ODataRuntimeException("Charset UTF-8 not found");
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchBodyPart.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchBodyPart.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchBodyPart.java
new file mode 100644
index 0000000..c2d2e0f
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchBodyPart.java
@@ -0,0 +1,135 @@
+/*
+ * 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.server.core.batch.parser;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
+
+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> message, final String boundary, final boolean isStrict) {
+    this.boundary = boundary;
+    this.isStrict = isStrict;
+    remainingMessage.addAll(message);
+  }
+
+  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 header) throws BatchException {
+    final List<String> contentTypes = headers.getHeaders(HttpHeader.CONTENT_TYPE);
+    boolean isChangeSet = false;
+
+    if (contentTypes.size() == 0) {
+      throw new BatchException("Missing content type", BatchException.MessageKeys.MISSING_CONTENT_TYPE, ""
+          + headers.getLineNumber());
+    }
+
+    for (String contentType : contentTypes) {
+      if (isContentTypeMultiPartMixed(contentType)) {
+        isChangeSet = true;
+      }
+    }
+
+    return isChangeSet;
+  }
+
+  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> remainingMessage2) 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> remainingMessage2) throws BatchException {
+
+    final HeaderField contentTypeField = headers.getHeaderField(HttpHeader.CONTENT_TYPE);
+    final String changeSetBoundary = BatchParserCommon.getBoundary(contentTypeField.getValueNotNull(),
+        contentTypeField.getLineNumber());
+    validateChangeSetBoundary(changeSetBoundary, headers);
+
+    return BatchParserCommon.splitMessageByBoundary(remainingMessage, changeSetBoundary);
+  }
+
+  private void validateChangeSetBoundary(final String changeSetBoundary, final Header header) throws BatchException {
+    if (changeSetBoundary.equals(boundary)) {
+      throw new BatchException("Change set boundary is equals to batch request boundary",
+          BatchException.MessageKeys.INVALID_BOUNDARY,
+          "" + header.getHeaderField(HttpHeader.CONTENT_TYPE).getLineNumber());
+    }
+  }
+
+  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 boolean isContentTypeMultiPartMixed(final String contentType) {
+    return BatchParserCommon.PATTERN_MULTIPART_BOUNDARY.matcher(contentType).matches();
+  }
+
+  @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-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchChangeSetPart.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchChangeSetPart.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchChangeSetPart.java
new file mode 100644
index 0000000..1d0bd6f
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/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.server.core.batch.parser;
+
+import java.util.List;
+
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.parser.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-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java
new file mode 100644
index 0000000..37b1a9c
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java
@@ -0,0 +1,91 @@
+/*
+ * 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.server.core.batch.parser;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.olingo.commons.api.ODataRuntimeException;
+import org.apache.olingo.server.api.batch.BatchParserResult;
+import org.apache.olingo.server.api.batch.BatchRequestPart;
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
+import org.apache.olingo.server.core.batch.transformator.BatchRequestTransformator;
+
+public class BatchParser {
+
+  private final String contentTypeMime;
+  private final String baseUri;
+  private final String rawServiceResolutionUri;
+  private final boolean isStrict;
+  
+  public BatchParser(final String contentType, final String baseUri, final String serviceResolutionUri, 
+      final boolean isStrict) {
+    contentTypeMime = contentType;
+    this.baseUri = BatchParserCommon.removeEndingSlash(baseUri);
+    this.isStrict = isStrict;
+    this.rawServiceResolutionUri = serviceResolutionUri;
+  }
+
+  @SuppressWarnings("unchecked")
+  public List<BatchRequestPart> parseBatchRequest(final InputStream in) throws BatchException {
+    return (List<BatchRequestPart>) parse(in, new BatchRequestTransformator(baseUri, rawServiceResolutionUri));
+  }
+
+  private List<? extends BatchParserResult> parse(final InputStream in, final BatchRequestTransformator 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 BatchRequestTransformator transformator)
+      throws IOException, BatchException {
+    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));
+    }
+
+    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.toLineList();
+    reader.close();
+
+    return BatchParserCommon.splitMessageByBoundary(message, boundary);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
new file mode 100644
index 0000000..e2dc5e5
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
@@ -0,0 +1,228 @@
+/*
+ * 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.server.core.batch.parser;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.olingo.commons.api.http.HttpContentType;
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
+
+public class BatchParserCommon {
+
+  private static final String REG_EX_BOUNDARY =
+      "([a-zA-Z0-9_\\-\\.'\\+]{1,70})|\"([a-zA-Z0-9_\\-\\.'\\+\\s\\" +
+          "(\\),/:=\\?]{1,69}[a-zA-Z0-9_\\-\\.'\\+\\(\\),/:=\\?])\"";
+  private static final Pattern PATTERN_LAST_CRLF = Pattern.compile("(.*)(\r\n){1}( *)", Pattern.DOTALL);
+  private static final Pattern PATTERN_HEADER_LINE = Pattern.compile("([a-zA-Z\\-]+):\\s?(.*)\\s*");
+  private static final String REG_EX_APPLICATION_HTTP = "application/http";
+  
+  public static final Pattern PATTERN_MULTIPART_BOUNDARY = Pattern.compile("multipart/mixed(.*)",
+      Pattern.CASE_INSENSITIVE);
+  public static final Pattern PATTERN_CONTENT_TYPE_APPLICATION_HTTP = Pattern.compile(REG_EX_APPLICATION_HTTP,
+      Pattern.CASE_INSENSITIVE);
+  public static final String BINARY_ENCODING = "binary";
+  public static final String HTTP_CONTENT_ID = "Content-Id";
+  public static final String HTTP_CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
+  
+  public static final String HTTP_EXPECT = "Expect";
+  public static final String HTTP_FROM = "From";
+  public static final String HTTP_MAX_FORWARDS = "Max-Forwards";
+  public static final String HTTP_RANGE = "Range";
+  public static final String HTTP_TE = "TE";
+  
+  public static String getBoundary(final String contentType, final int line) throws BatchException {
+    if (contentType.toLowerCase(Locale.ENGLISH).startsWith("multipart/mixed")) {
+      final String[] parameter = contentType.split(";");
+
+      for (final String pair : parameter) {
+
+        final String[] attrValue = pair.split("=");
+        if (attrValue.length == 2 && "boundary".equals(attrValue[0].trim().toLowerCase(Locale.ENGLISH))) {
+          if (attrValue[1].matches(REG_EX_BOUNDARY)) {
+            return trimQuota(attrValue[1].trim());
+          } else {
+            throw new BatchException("Invalid boundary format", BatchException.MessageKeys.INVALID_BOUNDARY, "" + line);
+          }
+        }
+
+      }
+    }
+    throw new BatchException("Content type is not multipart mixed", 
+        BatchException.MessageKeys.INVALID_CONTENT_TYPE, HttpContentType.MULTIPART_MIXED);
+  }
+
+  public static String removeEndingSlash(String content) {
+    content = content.trim();
+    int lastSlashIndex = content.lastIndexOf('/');
+
+    return (lastSlashIndex == content.length() - 1) ? content.substring(0, content.length() - 1) : content;
+  }
+
+  private static String trimQuota(String boundary) {
+    if (boundary.matches("\".*\"")) {
+      boundary = boundary.replace("\"", "");
+    }
+
+    return boundary;
+  }
+
+  public 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("Missing boundary delimiter", BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER, ""
+          + lineNumer);
+    }
+
+    if (!isEndReached) {
+      throw new BatchException("Missing close boundary delimiter", BatchException.MessageKeys.MISSING_CLOSE_DELIMITER,
+          "" + lineNumer);
+    }
+
+    if (messageParts.size() == 0) {
+      throw new BatchException("No boundary delimiter found",
+          BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER, boundary, "" + 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) {
+    final int headerLineNumber = remainingMessage.size() != 0 ? remainingMessage.get(0).getLineNumber() : 0;
+    final Header headers = new Header(headerLineNumber);
+    final Iterator<Line> iter = remainingMessage.iterator();
+    Line currentLine;
+    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();
+
+        headers.addHeader(headerName, Header.splitValuesByComma(headerValue), currentLine.getLineNumber());
+      } else {
+        isHeader = false;
+      }
+    }
+
+    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("Missing blank line", BatchException.MessageKeys.MISSING_BLANK_LINE, "[None]", ""
+            + lineNumber);
+      }
+    }
+  }
+
+  public static InputStream convertLineListToInputStream(List<Line> messageList) {
+    final String message = lineListToString(messageList);
+
+    return new ByteArrayInputStream(message.getBytes());
+  }
+
+  private static String lineListToString(List<Line> messageList) {
+    final StringBuilder builder = new StringBuilder();
+
+    for (Line currentLine : messageList) {
+      builder.append(currentLine.toString());
+    }
+
+    return builder.toString();
+  }
+  
+  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 InputStream convertLineListToInputStream(List<Line> list, int length) {
+    final String message = trimLineListToLength(list, length);
+
+    return new ByteArrayInputStream(message.getBytes());
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchPart.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchPart.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchPart.java
new file mode 100644
index 0000000..104c152
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/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.server.core.batch.parser;
+
+public interface BatchPart {
+  public Header getHeaders();
+
+  public boolean isStrict();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchQueryOperation.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchQueryOperation.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchQueryOperation.java
new file mode 100644
index 0000000..5ff7faf
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/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.server.core.batch.parser;
+
+import java.util.List;
+
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.parser.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("Missing http request line", BatchException.MessageKeys.INVALID_STATUS_LINE, "" + 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-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchRequestPartImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchRequestPartImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchRequestPartImpl.java
new file mode 100644
index 0000000..6c80216
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchRequestPartImpl.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.parser;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.batch.BatchRequestPart;
+
+public class BatchRequestPartImpl implements BatchRequestPart {
+
+  private List<ODataRequest> requests = new ArrayList<ODataRequest>();
+  private boolean isChangeSet;
+  
+  public BatchRequestPartImpl(final boolean isChangeSet, final List<ODataRequest> requests) {
+    this.isChangeSet = isChangeSet;
+    this.requests = requests;
+  }
+
+  @Override
+  public boolean isChangeSet() {
+    return isChangeSet;
+  }
+
+  @Override
+  public List<ODataRequest> getRequests() {
+    return Collections.unmodifiableList(requests);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndings.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndings.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndings.java
new file mode 100644
index 0000000..aebf13b
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndings.java
@@ -0,0 +1,286 @@
+/*
+ * 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.server.core.batch.parser;
+
+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<String> toList() throws IOException {
+    final List<String> result = new ArrayList<String>();
+    String currentLine;
+    
+    while ((currentLine = readLine()) != null) {
+      result.add(currentLine);
+    }
+
+    return result;
+  }
+  
+  public List<Line> toLineList() 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-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/Header.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/Header.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/Header.java
new file mode 100644
index 0000000..2ac009d
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/Header.java
@@ -0,0 +1,181 @@
+/*
+ * 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.server.core.batch.parser;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+public class Header implements Iterable<HeaderField> {
+  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 void replaceHeaderField(final HeaderField headerField) {
+    headers.put(headerField.getFieldName().toLowerCase(Locale.ENGLISH), headerField);
+  }
+
+  public boolean exists(final String name) {
+    final HeaderField field = headers.get(name.toLowerCase(Locale.ENGLISH));
+    
+    return field != null && field.getValues().size() != 0;
+  }
+
+  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;
+  }
+
+  @Override
+  public Iterator<HeaderField> iterator() {
+    return new Iterator<HeaderField>() {
+      Iterator<String> keyIterator = headers.keySet().iterator();
+
+      @Override
+      public boolean hasNext() {
+        return keyIterator.hasNext();
+      }
+
+      @Override
+      public HeaderField next() {
+        return headers.get(keyIterator.next());
+      }
+      
+      @Override
+      public void remove() {
+        throw new UnsupportedOperationException();
+      }
+    };
+  }
+
+  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;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/HeaderField.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/HeaderField.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/HeaderField.java
new file mode 100644
index 0000000..4cad817
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/HeaderField.java
@@ -0,0 +1,121 @@
+/*
+ * 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.server.core.batch.parser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+  public 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;
+    }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
new file mode 100644
index 0000000..02c8118
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
@@ -0,0 +1,191 @@
+/*
+ * 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.server.core.batch.transformator;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.batch.BatchParserResult;
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.core.batch.parser.BatchBodyPart;
+import org.apache.olingo.server.core.batch.parser.BatchChangeSetPart;
+import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
+import org.apache.olingo.server.core.batch.parser.BatchPart;
+import org.apache.olingo.server.core.batch.parser.BatchQueryOperation;
+import org.apache.olingo.server.core.batch.parser.BatchRequestPartImpl;
+import org.apache.olingo.server.core.batch.parser.Header;
+import org.apache.olingo.server.core.batch.parser.HeaderField;
+import org.apache.olingo.server.core.batch.transformator.BatchTransformatorCommon.HttpRequestStatusLine;
+
+public class BatchRequestTransformator implements BatchTransformator {
+  private final String baseUri;
+  private final String rawServiceResolutionUri;
+
+  public BatchRequestTransformator(final String baseUri, final String serviceResolutionUri) {
+    this.baseUri = baseUri;
+    this.rawServiceResolutionUri = serviceResolutionUri;
+  }
+
+  @Override
+  public List<BatchParserResult> transform(final BatchBodyPart bodyPart) throws BatchException {
+    final List<ODataRequest> requests = new LinkedList<ODataRequest>();
+    final List<BatchParserResult> resultList = new ArrayList<BatchParserResult>();
+
+    validateBodyPartHeader(bodyPart);
+
+    for (BatchQueryOperation queryOperation : bodyPart.getRequests()) {
+      requests.add(processQueryOperation(bodyPart, baseUri, queryOperation));
+    }
+
+    resultList.add(new BatchRequestPartImpl(bodyPart.isChangeSet(), requests));
+    return resultList;
+  }
+
+  private ODataRequest
+      processQueryOperation(BatchBodyPart bodyPart, String baseUri, BatchQueryOperation queryOperation)
+          throws BatchException {
+    if (bodyPart.isChangeSet()) {
+      BatchQueryOperation encapsulatedQueryOperation = ((BatchChangeSetPart) queryOperation).getRequest();
+      handleContentId(queryOperation, encapsulatedQueryOperation);
+      validateHeader(queryOperation, true);
+
+      return createRequest(encapsulatedQueryOperation, baseUri, bodyPart.isChangeSet());
+    } else {
+      return createRequest(queryOperation, baseUri, bodyPart.isChangeSet());
+    }
+  }
+
+  private void handleContentId(BatchQueryOperation changeRequestPart, BatchQueryOperation request)
+      throws BatchException {
+    final HeaderField contentIdChangeRequestPart = getContentId(changeRequestPart);
+    final HeaderField contentIdRequest = getContentId(request);
+
+    if (contentIdChangeRequestPart == null && contentIdRequest == null) {
+      throw new BatchException("Missing content type", MessageKeys.MISSING_CONTENT_ID, changeRequestPart.getHeaders()
+          .getLineNumber());
+    } else if (contentIdChangeRequestPart != null) {
+      request.getHeaders().replaceHeaderField(contentIdChangeRequestPart);
+    }
+  }
+
+  private HeaderField getContentId(final BatchQueryOperation queryOperation) throws BatchException {
+    final HeaderField contentTypeHeader = queryOperation.getHeaders().getHeaderField(BatchParserCommon.HTTP_CONTENT_ID);
+
+    if (contentTypeHeader != null) {
+      if (contentTypeHeader.getValues().size() == 1) {
+        return contentTypeHeader;
+      } else {
+        throw new BatchException("Invalid header", MessageKeys.INVALID_HEADER, contentTypeHeader.getLineNumber());
+      }
+    }
+
+    return null;
+  }
+
+  private ODataRequest createRequest(BatchQueryOperation operation, String baseUri, boolean isChangeSet)
+      throws BatchException {
+    final HttpRequestStatusLine statusLine =
+        new HttpRequestStatusLine(operation.getHttpStatusLine(), baseUri, rawServiceResolutionUri, operation
+            .getHeaders());
+    statusLine.validateHttpMethod(isChangeSet);
+
+    validateBody(statusLine, operation);
+    InputStream bodyStrean = getBodyStream(operation, statusLine);
+
+    validateForbiddenHeader(operation);
+
+    final ODataRequest request = new ODataRequest();
+    request.setBody(bodyStrean);
+    request.setMethod(statusLine.getMethod());
+    request.setRawBaseUri(statusLine.getRawBaseUri());
+    request.setRawODataPath(statusLine.getRawODataPath());
+    request.setRawQueryPath(statusLine.getRawQueryPath());
+    request.setRawRequestUri(statusLine.getRawRequestUri());
+    request.setRawServiceResolutionUri(statusLine.getRawServiceResolutionUri());
+
+    for (final HeaderField field : operation.getHeaders()) {
+      request.addHeader(field.getFieldName(), field.getValues());
+    }
+
+    return request;
+  }
+
+  private void validateForbiddenHeader(BatchQueryOperation operation) throws BatchException {
+    final Header header = operation.getHeaders();
+
+    if (header.exists(HttpHeader.AUTHORIZATION) || header.exists(BatchParserCommon.HTTP_EXPECT)
+        || header.exists(BatchParserCommon.HTTP_FROM) || header.exists(BatchParserCommon.HTTP_MAX_FORWARDS)
+        || header.exists(BatchParserCommon.HTTP_RANGE) || header.exists(BatchParserCommon.HTTP_TE)) {
+      throw new BatchException("Forbidden header", MessageKeys.FORBIDDEN_HEADER, header.getLineNumber());
+    }
+  }
+
+  private InputStream getBodyStream(BatchQueryOperation operation, HttpRequestStatusLine statusLine)
+      throws BatchException {
+    if (statusLine.getMethod().equals(HttpMethod.GET)) {
+      return new ByteArrayInputStream(new byte[0]);
+    } else {
+      int contentLength = BatchTransformatorCommon.getContentLength(operation.getHeaders());
+
+      if (contentLength == -1) {
+        return BatchParserCommon.convertLineListToInputStream(operation.getBody());
+      } else {
+        return BatchParserCommon.convertLineListToInputStream(operation.getBody(), contentLength);
+      }
+    }
+  }
+
+  private void validateBody(HttpRequestStatusLine statusLine, BatchQueryOperation operation) throws BatchException {
+    if (statusLine.getMethod().equals(HttpMethod.GET) && isUnvalidGetRequestBody(operation)) {
+      throw new BatchException("Invalid request line", MessageKeys.INVALID_CONTENT, statusLine.getLineNumber());
+    }
+  }
+
+  private boolean isUnvalidGetRequestBody(final BatchQueryOperation operation) {
+    return (operation.getBody().size() > 1)
+        || (operation.getBody().size() == 1 && !"".equals(operation.getBody().get(0).toString().trim()));
+  }
+
+  private void validateHeader(BatchPart bodyPart, boolean isChangeSet) throws BatchException {
+    final Header headers = bodyPart.getHeaders();
+
+    BatchTransformatorCommon.validateContentType(headers, BatchParserCommon.PATTERN_CONTENT_TYPE_APPLICATION_HTTP);
+    if (isChangeSet) {
+      BatchTransformatorCommon.validateContentTransferEncoding(headers);
+    }
+  }
+
+  private void validateBodyPartHeader(BatchBodyPart bodyPart) throws BatchException {
+    final Header header = bodyPart.getHeaders();
+
+    if (bodyPart.isChangeSet()) {
+      BatchTransformatorCommon.validateContentType(header, BatchParserCommon.PATTERN_MULTIPART_BOUNDARY);
+    } else {
+      BatchTransformatorCommon.validateContentTransferEncoding(header);
+      BatchTransformatorCommon.validateContentType(header, BatchParserCommon.PATTERN_CONTENT_TYPE_APPLICATION_HTTP);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformator.java
new file mode 100644
index 0000000..becb6c7
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformator.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.server.core.batch.transformator;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.batch.BatchParserResult;
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.parser.BatchBodyPart;
+
+public interface BatchTransformator {
+  public List<BatchParserResult> transform(BatchBodyPart bodyPart) throws BatchException;
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
new file mode 100644
index 0000000..a351cca
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
@@ -0,0 +1,250 @@
+/*
+ * 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.server.core.batch.transformator;
+
+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.commons.api.http.HttpContentType;
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
+import org.apache.olingo.server.core.batch.parser.Header;
+import org.apache.olingo.server.core.batch.parser.HeaderField;
+
+public class BatchTransformatorCommon {
+
+  public static void validateContentType(final Header headers, final Pattern pattern) throws BatchException {
+    List<String> contentTypes = headers.getHeaders(HttpHeader.CONTENT_TYPE);
+
+    if (contentTypes.size() == 0) {
+      throw new BatchException("Missing content type", MessageKeys.MISSING_CONTENT_TYPE, headers.getLineNumber());
+    }
+    if (!headers.isHeaderMatching(HttpHeader.CONTENT_TYPE, pattern)) {
+
+      throw new BatchException("Invalid content type", MessageKeys.INVALID_CONTENT_TYPE,
+          HttpContentType.MULTIPART_MIXED + " or " + HttpContentType.APPLICATION_HTTP);
+    }
+  }
+
+  public static void validateContentTransferEncoding(Header headers) throws BatchException {
+    final HeaderField contentTransferField = headers.getHeaderField(BatchParserCommon.HTTP_CONTENT_TRANSFER_ENCODING);
+
+    if (contentTransferField != null) {
+      final List<String> contentTransferValues = contentTransferField.getValues();
+      if (contentTransferValues.size() == 1) {
+        String encoding = contentTransferValues.get(0);
+
+        if (!BatchParserCommon.BINARY_ENCODING.equalsIgnoreCase(encoding)) {
+          throw new BatchException("Invalid content transfer encoding", MessageKeys.INVALID_CONTENT_TRANSFER_ENCODING,
+              headers.getLineNumber());
+        }
+      } else {
+        throw new BatchException("Invalid header", MessageKeys.INVALID_HEADER, headers.getLineNumber());
+      }
+    } else {
+      throw new BatchException("Missing mandatory content transfer encoding",
+          MessageKeys.MISSING_CONTENT_TRANSFER_ENCODING,
+          headers.getLineNumber());
+    }
+  }
+
+  public static int getContentLength(Header headers) throws BatchException {
+    final HeaderField contentLengthField = headers.getHeaderField(HttpHeader.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("Invalid content length", MessageKeys.INVALID_CONTENT_LENGTH, contentLengthField
+              .getLineNumber());
+        }
+
+        return contentLength;
+      } catch (NumberFormatException e) {
+        throw new BatchException("Invalid header", MessageKeys.INVALID_HEADER, contentLengthField.getLineNumber());
+      }
+    }
+
+    return -1;
+  }
+
+  public static class HttpRequestStatusLine {
+    private static final Pattern PATTERN_RELATIVE_URI = Pattern.compile("([^/][^?]*)(?:\\?(.*))?");
+    private static final Pattern PATTERN_ABSOLUTE_URI_WITH_HOST = Pattern.compile("(/[^?]*)(?:\\?(.*))?");
+    private static final Pattern PATTERN_ABSOLUTE_URI = Pattern.compile("(http[s]?://[^?]*)(?:\\?(.*))?");
+
+    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;
+
+    private HttpMethod method;
+    private String httpVersion;
+    private String rawServiceResolutionUri;
+    private String rawQueryPath;
+    private String rawODataPath;
+    private String rawBaseUri;
+    private Header header;
+    private String rawRequestUri;
+
+    public HttpRequestStatusLine(final Line httpStatusLine, final String baseUri, final String serviceResolutionUri,
+        final Header requestHeader)
+        throws BatchException {
+      statusLine = httpStatusLine;
+      requestBaseUri = baseUri;
+      header = requestHeader;
+      rawServiceResolutionUri = serviceResolutionUri;
+      
+      parse();
+    }
+
+    private void parse() throws BatchException {
+      final String[] parts = statusLine.toString().split(" ");
+
+      if (parts.length == 3) {
+        method = parseMethod(parts[0]);
+        parseRawPaths(parts[1]);
+        httpVersion = parseHttpVersion(parts[2]);
+      } else {
+        throw new BatchException("Invalid status line", MessageKeys.INVALID_STATUS_LINE, statusLine.getLineNumber());
+      }
+    }
+
+    private HttpMethod parseMethod(final String method) throws BatchException {
+      try {
+        return HttpMethod.valueOf(method.trim());
+      } catch (IllegalArgumentException e) {
+        throw new BatchException("Illegal http method", MessageKeys.INVALID_METHOD, statusLine.getLineNumber());
+      }
+    }
+
+    private void parseRawPaths(final String rawUrl) throws BatchException {
+      final Matcher absoluteUriMatcher = PATTERN_ABSOLUTE_URI.matcher(rawUrl);
+      final Matcher absoluteUriWtithHostMatcher = PATTERN_ABSOLUTE_URI_WITH_HOST.matcher(rawUrl);
+      final Matcher relativeUriMatcher = PATTERN_RELATIVE_URI.matcher(rawUrl);
+
+      if (absoluteUriMatcher.matches()) {
+        buildUri(absoluteUriMatcher.group(1), absoluteUriMatcher.group(2));
+
+      } else if (absoluteUriWtithHostMatcher.matches()) {
+        final List<String> hostHeader = header.getHeaders(HttpHeader.HOST);
+        if (hostHeader.size() == 1) {
+          buildUri(hostHeader.get(0) + absoluteUriWtithHostMatcher.group(1), absoluteUriWtithHostMatcher.group(2));
+        } else {
+          throw new BatchException("Exactly one host header is required", MessageKeys.MISSING_MANDATORY_HEADER,
+              statusLine.getLineNumber());
+        }
+
+      } else if (relativeUriMatcher.matches()) {
+        buildUri(requestBaseUri + "/" + relativeUriMatcher.group(1), relativeUriMatcher.group(2));
+
+      } else {
+        throw new BatchException("Invalid uri", MessageKeys.INVALID_URI, statusLine.getLineNumber());
+      }
+    }
+
+    private void buildUri(final String resourceUri, final String queryOptions) throws BatchException {
+      if(!resourceUri.startsWith(requestBaseUri)) {
+        throw new BatchException("Host do not match", MessageKeys.INVALID_URI, statusLine.getLineNumber());
+      }
+      
+      final int oDataPathIndex = resourceUri.indexOf(requestBaseUri);
+
+      rawBaseUri = requestBaseUri;
+      rawODataPath = resourceUri.substring(oDataPathIndex + requestBaseUri.length());
+      rawServiceResolutionUri = "";
+      rawRequestUri = requestBaseUri + rawODataPath;
+
+      if (queryOptions != null) {
+        rawRequestUri += "?" + queryOptions;
+        rawQueryPath = queryOptions;
+      } else {
+        rawQueryPath = "";
+      }
+    }
+
+    private String parseHttpVersion(final String httpVersion) throws BatchException {
+      if (!HTTP_VERSION.equals(httpVersion.trim())) {
+        throw new BatchException("Invalid http version", MessageKeys.INVALID_HTTP_VERSION, 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("Invalid change set method", MessageKeys.INVALID_CHANGESET_METHOD, statusLine
+              .getLineNumber());
+        } else {
+          throw new BatchException("Invalid query operation method", MessageKeys.INVALID_QUERY_OPERATION_METHOD,
+              statusLine.getLineNumber());
+        }
+      }
+    }
+
+    public HttpMethod getMethod() {
+      return method;
+    }
+
+    public String getHttpVersion() {
+      return httpVersion;
+    }
+
+    public int getLineNumber() {
+      return statusLine.getLineNumber();
+    }
+
+    public String getRawBaseUri() {
+      return rawBaseUri;
+    }
+
+    public String getRawODataPath() {
+      return rawODataPath;
+    }
+
+    public String getRawQueryPath() {
+      return rawQueryPath;
+    }
+
+    public String getRawRequestUri() {
+      return rawRequestUri;
+    }
+
+    public String getRawServiceResolutionUri() {
+      return rawServiceResolutionUri;
+    }
+  }
+}


Mime
View raw message