You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by mi...@apache.org on 2014/10/14 20:34:52 UTC
[05/39] git commit: Batch Parser
Batch Parser
Signed-off-by: Christian Amend <ch...@apache.org>
Project: http://git-wip-us.apache.org/repos/asf/olingo-odata2/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata2/commit/6eca235e
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata2/tree/6eca235e
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata2/diff/6eca235e
Branch: refs/heads/Olingo-129_PocJpaDataStore
Commit: 6eca235ea24a19dbd578109b6590f435fe16e2d4
Parents: 4966ebe
Author: Christian Holzer <c....@sap.com>
Authored: Wed Aug 20 17:53:10 2014 +0200
Committer: Christian Amend <ch...@apache.org>
Committed: Tue Sep 23 14:47:37 2014 +0200
----------------------------------------------------------------------
.../olingo/odata2/api/batch/BatchException.java | 6 +-
.../odata2/api/batch/BatchParserResult.java | 5 +
.../odata2/api/batch/BatchRequestPart.java | 2 +-
.../odata2/api/batch/BatchResponsePart.java | 2 +-
.../api/client/batch/BatchSingleResponse.java | 4 +-
.../odata2/core/batch/BatchRequestParser.java | 614 -------------------
.../odata2/core/batch/BatchResponseParser.java | 356 -----------
.../odata2/core/batch/v2/BatchBodyPart.java | 155 +++++
.../odata2/core/batch/v2/BatchChangeSet.java | 55 ++
.../odata2/core/batch/v2/BatchParser.java | 130 ++++
.../odata2/core/batch/v2/BatchParserCommon.java | 414 +++++++++++++
.../olingo/odata2/core/batch/v2/BatchPart.java | 29 +
.../core/batch/v2/BatchQueryOperation.java | 82 +++
.../batch/v2/BatchRequestTransformator.java | 253 ++++++++
.../batch/v2/BatchResponseTransformator.java | 134 ++++
.../core/batch/v2/BatchTransformator.java | 30 +
.../core/batch/v2/BatchTransformatorCommon.java | 84 +++
.../v2/BufferedReaderIncludingLineEndings.java | 220 +++++++
.../odata2/core/ep/ProviderFacadeImpl.java | 7 +-
.../src/main/resources/i18n.properties | 1 +
.../core/batch/BatchParserCommonTest.java | 99 +++
.../core/batch/BatchRequestParserTest.java | 526 +++++++++++++++-
.../odata2/core/batch/BatchRequestTest.java | 48 +-
.../core/batch/BatchResponseParserTest.java | 34 +-
.../odata2/core/batch/BatchResponseTest.java | 13 +-
.../core/batch/BatchResponseWriterTest.java | 2 +-
.../batch/BatchTransformatorCommonTest.java | 95 +++
.../BufferedReaderIncludingLineEndingsTest.java | 452 ++++++++++++++
.../src/test/resources/batchWithPost.batch | 1 +
.../odata2/fit/client/ClientBatchTest.java | 2 +
.../fit/client/ClientDeltaResponseTest.java | 2 +
.../src/test/resources/batchWithContentId.batch | 2 +
.../resources/batchWithContentIdPart2.batch | 6 +-
.../src/test/resources/changeset.batch | 2 +
34 files changed, 2826 insertions(+), 1041 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchException.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchException.java b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchException.java
index 647b071..1171719 100644
--- a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchException.java
+++ b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchException.java
@@ -43,7 +43,11 @@ public class BatchException extends ODataMessageException {
/** MISSING_CLOSE_DELIMITER requires 1 content value ('line number') */
public static final MessageReference MISSING_CLOSE_DELIMITER = createMessageReference(BatchException.class,
"MISSING_CLOSE_DELIMITER");
-
+
+ /** MISSONG MANDATORY HEADER requires 1 content value ('header name') */
+ public static final MessageReference MISSING_MANDATORY_HEADER = createMessageReference(BatchException.class,
+ "MISSING_MANDATORY_HEADER");
+
/** INVALID_QUERY_OPERATION_METHOD requires 1 content value ('line number') */
public static final MessageReference INVALID_QUERY_OPERATION_METHOD = createMessageReference(BatchException.class,
"INVALID_QUERY_OPERATION_METHOD");
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchParserResult.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchParserResult.java b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchParserResult.java
new file mode 100644
index 0000000..e11b69e
--- /dev/null
+++ b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchParserResult.java
@@ -0,0 +1,5 @@
+package org.apache.olingo.odata2.api.batch;
+
+public interface BatchParserResult {
+
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchRequestPart.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchRequestPart.java b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchRequestPart.java
index 5e3e2f2..5f76a36 100644
--- a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchRequestPart.java
+++ b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchRequestPart.java
@@ -26,7 +26,7 @@ import org.apache.olingo.odata2.api.processor.ODataRequest;
* A BatchPart
* <p> BatchPart represents a distinct MIME part of a Batch Request body. It can be ChangeSet or Query Operation
*/
-public interface BatchRequestPart {
+public interface BatchRequestPart extends BatchParserResult {
/**
* Get the info if a BatchPart is a ChangeSet
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchResponsePart.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchResponsePart.java b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchResponsePart.java
index dfafbdb..6133104 100644
--- a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchResponsePart.java
+++ b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/batch/BatchResponsePart.java
@@ -29,7 +29,7 @@ import org.apache.olingo.odata2.api.rt.RuntimeDelegate;
* response to a retrieve request
*
*/
-public abstract class BatchResponsePart {
+public abstract class BatchResponsePart implements BatchParserResult {
/**
* Get responses. If a BatchResponsePart is a response to a retrieve request, the list consists of one response.
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchSingleResponse.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchSingleResponse.java b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchSingleResponse.java
index dc8c9b7..ddb3c02 100644
--- a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchSingleResponse.java
+++ b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/client/batch/BatchSingleResponse.java
@@ -21,12 +21,14 @@ package org.apache.olingo.odata2.api.client.batch;
import java.util.Map;
import java.util.Set;
+import org.apache.olingo.odata2.api.batch.BatchParserResult;
+
/**
* A BatchSingleResponse
* <p> BatchSingleResponse represents a single response of a Batch Response body. It can be a response to a change
* request of ChangeSet or a response to a retrieve request
*/
-public interface BatchSingleResponse {
+public interface BatchSingleResponse extends BatchParserResult {
/**
* @return a result code of the attempt to understand and satisfy the request
*/
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestParser.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestParser.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestParser.java
deleted file mode 100644
index 6ac1445..0000000
--- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchRequestParser.java
+++ /dev/null
@@ -1,614 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ******************************************************************************/
-package org.apache.olingo.odata2.core.batch;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Scanner;
-import java.util.Set;
-import java.util.regex.MatchResult;
-import java.util.regex.Pattern;
-
-import org.apache.olingo.odata2.api.batch.BatchException;
-import org.apache.olingo.odata2.api.batch.BatchRequestPart;
-import org.apache.olingo.odata2.api.commons.HttpContentType;
-import org.apache.olingo.odata2.api.commons.HttpHeaders;
-import org.apache.olingo.odata2.api.commons.ODataHttpMethod;
-import org.apache.olingo.odata2.api.ep.EntityProviderBatchProperties;
-import org.apache.olingo.odata2.api.processor.ODataRequest;
-import org.apache.olingo.odata2.api.processor.ODataRequest.ODataRequestBuilder;
-import org.apache.olingo.odata2.api.uri.PathInfo;
-import org.apache.olingo.odata2.api.uri.PathSegment;
-import org.apache.olingo.odata2.core.ODataPathSegmentImpl;
-import org.apache.olingo.odata2.core.PathInfoImpl;
-import org.apache.olingo.odata2.core.commons.Decoder;
-import org.apache.olingo.odata2.core.exception.ODataRuntimeException;
-
-/**
- *
- */
-public class BatchRequestParser {
- private static final String LF = "\n";
- private static final String REG_EX_OPTIONAL_WHITESPACE = "\\s?";
- private static final String REG_EX_ZERO_OR_MORE_WHITESPACES = "\\s*";
- private static final String ANY_CHARACTERS = ".*";
-
- private static final Pattern REG_EX_BLANK_LINE = Pattern.compile("(|" + REG_EX_ZERO_OR_MORE_WHITESPACES + ")");
- private static final Pattern REG_EX_HEADER = Pattern.compile("([a-zA-Z\\-]+):" + REG_EX_OPTIONAL_WHITESPACE + "(.*)"
- + REG_EX_ZERO_OR_MORE_WHITESPACES);
- private static final Pattern REG_EX_VERSION = Pattern.compile("(?:HTTP/[0-9]\\.[0-9])");
- private static final Pattern REG_EX_ANY_BOUNDARY_STRING = Pattern.compile("--" + ANY_CHARACTERS
- + REG_EX_ZERO_OR_MORE_WHITESPACES);
- private static final Pattern REG_EX_REQUEST_LINE = Pattern.compile("(GET|POST|PUT|DELETE|MERGE|PATCH)\\s(.*)\\s?"
- + REG_EX_VERSION + REG_EX_ZERO_OR_MORE_WHITESPACES);
- private static final Pattern REG_EX_BOUNDARY_PARAMETER = Pattern.compile(REG_EX_OPTIONAL_WHITESPACE
- + "boundary=(\".*\"|.*)" + REG_EX_ZERO_OR_MORE_WHITESPACES);
- private static final Pattern REG_EX_CONTENT_TYPE = Pattern.compile(REG_EX_OPTIONAL_WHITESPACE
- + HttpContentType.MULTIPART_MIXED);
- private static final Pattern REG_EX_QUERY_PARAMETER = Pattern.compile("((?:\\$|)[^=]+)=([^=]+)");
-
- private static final String REG_EX_BOUNDARY =
- "([a-zA-Z0-9_\\-\\.'\\+]{1,70})|\"([a-zA-Z0-9_\\-\\.'\\+\\s\\" +
- "(\\),/:=\\?]{1,69}[a-zA-Z0-9_\\-\\.'\\+\\(\\),/:=\\?])\""; // See RFC 2046
-
- private String baseUri;
- private PathInfo batchRequestPathInfo;
- private String contentTypeMime;
- private String boundary;
- private String currentMimeHeaderContentId;
- private int currentLineNumber = 0;
- private final static Set<String> HTTP_CHANGESET_METHODS;
- private final static Set<String> HTTP_BATCH_METHODS;
-
- static {
- HashSet<String> httpChangesetMethods = new HashSet<String>();
- httpChangesetMethods.add("POST");
- httpChangesetMethods.add("PUT");
- httpChangesetMethods.add("DELETE");
- httpChangesetMethods.add("MERGE");
- httpChangesetMethods.add("PATCH");
- HTTP_CHANGESET_METHODS = Collections.unmodifiableSet(httpChangesetMethods);
-
- HashSet<String> httpBatchMethods = new HashSet<String>();
- httpBatchMethods.add("GET");
- HTTP_BATCH_METHODS = Collections.unmodifiableSet(httpBatchMethods);
- }
-
- public BatchRequestParser(final String contentType, final EntityProviderBatchProperties properties) {
- contentTypeMime = contentType;
- batchRequestPathInfo = properties.getPathInfo();
- }
-
- public List<BatchRequestPart> parse(final InputStream in) throws BatchException {
- Scanner scanner = new Scanner(in, BatchHelper.DEFAULT_ENCODING);
- scanner.useDelimiter(LF);
- baseUri = getBaseUri();
- List<BatchRequestPart> requestList;
- try {
- requestList = parseBatchRequest(scanner);
- } finally {// NOPMD (suppress DoNotThrowExceptionInFinally)
- scanner.close();
- try {
- in.close();
- } catch (IOException e) {
- throw new ODataRuntimeException(e);
- }
- }
- return requestList;
- }
-
- private List<BatchRequestPart> parseBatchRequest(final Scanner scanner) throws BatchException {
- List<BatchRequestPart> requests = new LinkedList<BatchRequestPart>();
- if (contentTypeMime != null) {
- boundary = getBoundary(contentTypeMime);
- parsePreamble(scanner);
- final String closeDelimiter = "--" + boundary + "--" + REG_EX_ZERO_OR_MORE_WHITESPACES;
- while (scanner.hasNext() && !scanner.hasNext(closeDelimiter)) {
- requests.add(parseMultipart(scanner, boundary, false));
- parseOptionalLine(scanner);
- }
- if (scanner.hasNext(closeDelimiter)) {
- scanner.next(closeDelimiter);
- currentLineNumber++;
- } else {
- throw new BatchException(BatchException.MISSING_CLOSE_DELIMITER.addContent(currentLineNumber));
- }
- } else {
- throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
- }
- return requests;
- }
-
- // The method parses additional information prior to the first boundary delimiter line
- private void parsePreamble(final Scanner scanner) {
- while (scanner.hasNext() && !scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
- scanner.next();
- currentLineNumber++;
- }
- }
-
- private BatchRequestPart parseMultipart(final Scanner scanner, final String boundary, final boolean isChangeSet)
- throws BatchException {
-
- if (scanner.hasNext("--" + boundary + REG_EX_ZERO_OR_MORE_WHITESPACES)) {
- scanner.next();
- currentLineNumber++;
- Map<String, String> mimeHeaders = parseHeaders(scanner);
- currentMimeHeaderContentId = mimeHeaders.get(BatchHelper.HTTP_CONTENT_ID.toLowerCase(Locale.ENGLISH));
-
- String contentType = mimeHeaders.get(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ENGLISH));
- if (contentType == null) {
- throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
- }
- if (isChangeSet) {
- return parseBatchRequestPartInChangeset(scanner, boundary, mimeHeaders, contentType);
- } else {
- return parseBatchRequestPart(scanner, boundary, mimeHeaders, contentType);
- }
- } else if (scanner.hasNext(boundary + REG_EX_ZERO_OR_MORE_WHITESPACES)) {
- currentLineNumber++;
- throw new BatchException(BatchException.INVALID_BOUNDARY_DELIMITER.addContent(currentLineNumber));
- } else if (scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
- currentLineNumber++;
- throw new BatchException(BatchException.NO_MATCH_WITH_BOUNDARY_STRING.addContent(boundary).addContent(
- currentLineNumber));
- } else {
- currentLineNumber++;
- throw new BatchException(BatchException.MISSING_BOUNDARY_DELIMITER.addContent(currentLineNumber));
- }
- }
-
- private BatchRequestPart parseBatchRequestPart(final Scanner scanner, final String boundary,
- final Map<String, String> mimeHeaders,
- final String contentType) throws BatchException {
- if (HttpContentType.APPLICATION_HTTP.equalsIgnoreCase(contentType)) {
- validateEncoding(mimeHeaders.get(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING.toLowerCase(Locale.ENGLISH)));
- parseNewLine(scanner);// mandatory
- List<ODataRequest> requests = new ArrayList<ODataRequest>(1);
- requests.add(parseRequest(scanner, false, boundary));
- return new BatchRequestPartImpl(false, requests);
- } else if (contentType.matches(REG_EX_OPTIONAL_WHITESPACE + HttpContentType.MULTIPART_MIXED + ANY_CHARACTERS)) {
- String changeSetBoundary = getBoundary(contentType);
- if (boundary.equals(changeSetBoundary)) {
- throw new BatchException(BatchException.INVALID_CHANGESET_BOUNDARY.addContent(currentLineNumber));
- }
- List<ODataRequest> changeSetRequests = new LinkedList<ODataRequest>();
- parseNewLine(scanner);// mandatory
- Pattern changeSetCloseDelimiter =
- Pattern.compile("--" + changeSetBoundary + "--" + REG_EX_ZERO_OR_MORE_WHITESPACES);
- while (!scanner.hasNext(changeSetCloseDelimiter)) {
- BatchRequestPart part = parseMultipart(scanner, changeSetBoundary, true);
- changeSetRequests.addAll(part.getRequests());
- }
- scanner.next(changeSetCloseDelimiter);
- currentLineNumber++;
- return new BatchRequestPartImpl(true, changeSetRequests);
- } else {
- throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED
- + " or " + HttpContentType.APPLICATION_HTTP));
- }
- }
-
- private BatchRequestPart parseBatchRequestPartInChangeset(final Scanner scanner, final String boundary,
- final Map<String, String> mimeHeaders,
- final String contentType) throws BatchException {
- if (HttpContentType.APPLICATION_HTTP.equalsIgnoreCase(contentType)) {
- validateEncoding(mimeHeaders.get(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING.toLowerCase(Locale.ENGLISH)));
- parseNewLine(scanner);// mandatory
- List<ODataRequest> requests = new ArrayList<ODataRequest>(1);
- requests.add(parseRequest(scanner, true, boundary));
- return new BatchRequestPartImpl(false, requests);
- } else {
- throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.APPLICATION_HTTP));
- }
- }
-
- private ODataRequest parseRequest(final Scanner scanner, final boolean isChangeSet, final String boundary)
- throws BatchException {
- if (scanner.hasNext(REG_EX_REQUEST_LINE)) {
- scanner.next(REG_EX_REQUEST_LINE);
- currentLineNumber++;
- final String method;
- final String uri;
- MatchResult result = scanner.match();
- if (result.groupCount() == 2) {
- method = result.group(1);
- uri = result.group(2).trim();
- } else {
- currentLineNumber++;
- throw new BatchException(BatchException.INVALID_REQUEST_LINE.addContent(scanner.next()).addContent(
- currentLineNumber));
- }
- PathInfo pathInfo = parseRequestUri(uri);
- Map<String, String> queryParameters = parseQueryParameters(uri);
- if (isChangeSet) {
- if (!HTTP_CHANGESET_METHODS.contains(method)) {
- throw new BatchException(BatchException.INVALID_CHANGESET_METHOD.addContent(currentLineNumber));
- }
- } else if (!HTTP_BATCH_METHODS.contains(method)) {
- throw new BatchException(BatchException.INVALID_QUERY_OPERATION_METHOD.addContent(currentLineNumber));
- }
- ODataHttpMethod httpMethod = ODataHttpMethod.valueOf(method);
- Map<String, List<String>> headers = parseRequestHeaders(scanner, boundary);
- if (currentMimeHeaderContentId != null) {
- List<String> headerList = new ArrayList<String>();
- headerList.add(currentMimeHeaderContentId);
- headers.put(BatchHelper.MIME_HEADER_CONTENT_ID.toLowerCase(Locale.ENGLISH), headerList);
- }
-
- String contentType = getContentTypeHeader(headers);
- List<String> acceptHeaders = getAcceptHeader(headers);
- List<Locale> acceptLanguages = getAcceptLanguageHeader(headers);
- InputStream body = new ByteArrayInputStream(new byte[0]);
- if (isChangeSet) {
- body = parseBody(scanner);
- }
-
- ODataRequestBuilder requestBuilder = ODataRequest.method(httpMethod)
- .queryParameters(queryParameters)
- .requestHeaders(headers)
- .pathInfo(pathInfo)
- .acceptableLanguages(acceptLanguages)
- .body(body)
- .acceptHeaders(acceptHeaders);
-
- if (contentType != null) {
- requestBuilder = requestBuilder.contentType(contentType);
- }
- return requestBuilder.build();
- } else {
- currentLineNumber++;
- throw new BatchException(BatchException.INVALID_REQUEST_LINE.addContent(scanner.next()).addContent(
- currentLineNumber));
- }
-
- }
-
- private Map<String, List<String>> parseRequestHeaders(final Scanner scanner, final String boundary)
- throws BatchException {
- Map<String, List<String>> headers = new HashMap<String, List<String>>();
- while (scanner.hasNext()
- && !scanner.hasNext(REG_EX_BLANK_LINE)
- && !scanner.hasNext("--" + boundary + REG_EX_ZERO_OR_MORE_WHITESPACES)) {
- if (scanner.hasNext(REG_EX_HEADER)) {
- scanner.next(REG_EX_HEADER);
- currentLineNumber++;
- MatchResult result = scanner.match();
- if (result.groupCount() == 2) {
- String headerName = result.group(1).trim().toLowerCase(Locale.ENGLISH);
- String headerValue = result.group(2).trim();
- if (HttpHeaders.ACCEPT.equalsIgnoreCase(headerName)) {
- List<String> acceptHeaders = parseAcceptHeaders(headerValue);
- headers.put(headerName, acceptHeaders);
- } else if (HttpHeaders.ACCEPT_LANGUAGE.equalsIgnoreCase(headerName)) {
- List<String> acceptLanguageHeaders = parseAcceptableLanguages(headerValue);
- headers.put(headerName, acceptLanguageHeaders);
- } else if (!BatchHelper.HTTP_CONTENT_ID.equalsIgnoreCase(headerName)) {
- if (headers.containsKey(headerName)) {
- headers.get(headerName).add(headerValue);
- } else {
- List<String> headerList = new ArrayList<String>();
- headerList.add(headerValue);
- headers.put(headerName, headerList);
- }
- } else {
- List<String> headerList = new ArrayList<String>();
- headerList.add(headerValue);
- headers.put(BatchHelper.REQUEST_HEADER_CONTENT_ID.toLowerCase(Locale.ENGLISH), headerList);
- }
- }
- } else {
- currentLineNumber++;
- throw new BatchException(BatchException.INVALID_HEADER.addContent(scanner.next())
- .addContent(currentLineNumber));
- }
- }
- return headers;
- }
-
- private PathInfo parseRequestUri(final String uri) throws BatchException {
- PathInfoImpl pathInfo = new PathInfoImpl();
- pathInfo.setServiceRoot(batchRequestPathInfo.getServiceRoot());
- pathInfo.setPrecedingPathSegment(batchRequestPathInfo.getPrecedingSegments());
- final String odataPathSegmentsAsString;
- final String queryParametersAsString;
- try {
- Scanner uriScanner = new Scanner(uri);
- uriScanner.useDelimiter(LF);
- URI uriObject = new URI(uri);
- if (uriObject.isAbsolute()) {
- Pattern regexRequestUri = Pattern.compile(baseUri + "/([^/][^?]*)(\\?.*)?");
- if (uriScanner.hasNext(regexRequestUri)) {
- uriScanner.next(regexRequestUri);
- MatchResult result = uriScanner.match();
- if (result.groupCount() == 2) {
- odataPathSegmentsAsString = result.group(1);
- queryParametersAsString = result.group(2) != null ? result.group(2) : "";
- } else {
- uriScanner.close();
- throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber));
- }
- } else {
- uriScanner.close();
- throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber));
- }
- } else {
- Pattern regexRequestUri = Pattern.compile("([^/][^?]*)(\\?.*)?");
- if (uriScanner.hasNext(regexRequestUri)) {
- uriScanner.next(regexRequestUri);
- MatchResult result = uriScanner.match();
- if (result.groupCount() == 2) {
- odataPathSegmentsAsString = result.group(1);
- queryParametersAsString = result.group(2) != null ? result.group(2) : "";
- } else {
- uriScanner.close();
- throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber));
- }
- } else if (uriScanner.hasNext("/(.*)")) {
- uriScanner.close();
- throw new BatchException(BatchException.UNSUPPORTED_ABSOLUTE_PATH.addContent(currentLineNumber));
- } else {
- uriScanner.close();
- throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber));
- }
-
- }
- uriScanner.close();
- pathInfo.setODataPathSegment(parseODataPathSegments(odataPathSegmentsAsString));
- if (!odataPathSegmentsAsString.startsWith("$")) {
- String requestUri = baseUri + "/" + odataPathSegmentsAsString + queryParametersAsString;
- pathInfo.setRequestUri(new URI(requestUri));
- }
- return pathInfo;
- } catch (URISyntaxException e) {
- throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber), e);
- }
-
- }
-
- private Map<String, String> parseQueryParameters(final String uri) throws BatchException {
- Scanner uriScanner = new Scanner(uri);
- uriScanner.useDelimiter("\n");
- Map<String, String> queryParametersMap = new HashMap<String, String>();
- Pattern regex = Pattern.compile("(?:" + baseUri + "/)?" + "[^?]+" + "\\?(.*)");
- if (uriScanner.hasNext(regex)) {
- uriScanner.next(regex);
- MatchResult uriResult = uriScanner.match();
- if (uriResult.groupCount() == 1) {
- String queryParams = uriResult.group(1);
- Scanner queryParamsScanner = new Scanner(queryParams);
- queryParamsScanner.useDelimiter("&");
- while (queryParamsScanner.hasNext(REG_EX_QUERY_PARAMETER)) {
- queryParamsScanner.next(REG_EX_QUERY_PARAMETER);
- MatchResult result = queryParamsScanner.match();
- if (result.groupCount() == 2) {
- String systemQueryOption = result.group(1);
- String value = result.group(2);
- queryParametersMap.put(systemQueryOption, Decoder.decode(value));
- } else {
- queryParamsScanner.close();
- throw new BatchException(BatchException.INVALID_QUERY_PARAMETER);
- }
- }
- queryParamsScanner.close();
-
- } else {
- uriScanner.close();
- throw new BatchException(BatchException.INVALID_URI.addContent(currentLineNumber));
- }
- }
- uriScanner.close();
- return queryParametersMap;
- }
-
- private List<PathSegment> parseODataPathSegments(final String odataPathSegmentsAsString) {
- Scanner pathSegmentScanner = new Scanner(odataPathSegmentsAsString);
- pathSegmentScanner.useDelimiter("/");
- List<PathSegment> odataPathSegments = new ArrayList<PathSegment>();
- while (pathSegmentScanner.hasNext()) {
- odataPathSegments.add(new ODataPathSegmentImpl(pathSegmentScanner.next(), null));
- }
- pathSegmentScanner.close();
- return odataPathSegments;
- }
-
- private List<String> parseAcceptHeaders(final String headerValue) throws BatchException {
- return AcceptParser.parseAcceptHeaders(headerValue);
- }
-
- private List<String> parseAcceptableLanguages(final String headerValue) throws BatchException {
- return AcceptParser.parseAcceptableLanguages(headerValue);
- }
-
- private InputStream parseBody(final Scanner scanner) {
- StringBuilder body = null;
- final InputStream requestBody;
-
- while (scanner.hasNext() && !scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
- if (!scanner.hasNext(REG_EX_ZERO_OR_MORE_WHITESPACES)) {
- if (body == null) {
- body = new StringBuilder(scanner.next());
- } else {
- body.append(LF).append(scanner.next());
- }
- } else {
- scanner.next();
- }
- currentLineNumber++;
- }
-
- if (body != null) {
- requestBody = new ByteArrayInputStream(BatchHelper.getBytes(body.toString()));
- } else {
- requestBody = new ByteArrayInputStream(new byte[0]);
- }
- return requestBody;
- }
-
- private String getBoundary(final String contentType) throws BatchException {
- Scanner contentTypeScanner = new Scanner(contentType);
- contentTypeScanner.useDelimiter(";\\s?");
- if (contentTypeScanner.hasNext(REG_EX_CONTENT_TYPE)) {
- contentTypeScanner.next(REG_EX_CONTENT_TYPE);
- } else {
- contentTypeScanner.close();
- throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED));
- }
- if (contentTypeScanner.hasNext(REG_EX_BOUNDARY_PARAMETER)) {
- contentTypeScanner.next(REG_EX_BOUNDARY_PARAMETER);
- MatchResult result = contentTypeScanner.match();
- contentTypeScanner.close();
- if (result.groupCount() == 1 && result.group(1).trim().matches(REG_EX_BOUNDARY)) {
- return trimQuota(result.group(1).trim());
- } else {
- throw new BatchException(BatchException.INVALID_BOUNDARY);
- }
- } else {
- contentTypeScanner.close();
- throw new BatchException(BatchException.MISSING_PARAMETER_IN_CONTENT_TYPE);
- }
- }
-
- private void validateEncoding(final String encoding) throws BatchException {
- if (!BatchHelper.BINARY_ENCODING.equalsIgnoreCase(encoding)) {
- throw new BatchException(BatchException.INVALID_CONTENT_TRANSFER_ENCODING);
- }
- }
-
- private Map<String, String> parseHeaders(final Scanner scanner) throws BatchException {
- Map<String, String> headers = new HashMap<String, String>();
- while (scanner.hasNext() && !(scanner.hasNext(REG_EX_BLANK_LINE))) {
- if (scanner.hasNext(REG_EX_HEADER)) {
- scanner.next(REG_EX_HEADER);
- currentLineNumber++;
- MatchResult result = scanner.match();
- if (result.groupCount() == 2) {
- String headerName = result.group(1).trim().toLowerCase(Locale.ENGLISH);
- String headerValue = result.group(2).trim();
- headers.put(headerName, headerValue);
- }
- } else {
- throw new BatchException(BatchException.INVALID_HEADER.addContent(scanner.next()));
- }
- }
- return headers;
- }
-
- private void parseNewLine(final Scanner scanner) throws BatchException {
- if (scanner.hasNext() && scanner.hasNext(REG_EX_BLANK_LINE)) {
- scanner.next();
- currentLineNumber++;
- } else {
- currentLineNumber++;
- if (scanner.hasNext()) {
- throw new BatchException(BatchException.MISSING_BLANK_LINE.addContent(scanner.next()).addContent(
- currentLineNumber));
- } else {
- throw new BatchException(BatchException.TRUNCATED_BODY.addContent(currentLineNumber));
-
- }
- }
- }
-
- private void parseOptionalLine(final Scanner scanner) throws BatchException {
- while (scanner.hasNext() && scanner.hasNext(REG_EX_BLANK_LINE)) {
- scanner.next();
- currentLineNumber++;
- }
- }
-
- private String getBaseUri() throws BatchException {
- if (batchRequestPathInfo != null) {
- if (batchRequestPathInfo.getServiceRoot() != null) {
- String baseUri = batchRequestPathInfo.getServiceRoot().toASCIIString();
- if (baseUri.lastIndexOf('/') == baseUri.length() - 1) {
- baseUri = baseUri.substring(0, baseUri.length() - 1);
- }
- for (PathSegment precedingPS : batchRequestPathInfo.getPrecedingSegments()) {
- baseUri = baseUri + "/" + precedingPS.getPath();
- }
- return baseUri;
- }
- } else {
- throw new BatchException(BatchException.INVALID_PATHINFO);
- }
- return null;
- }
-
- private String trimQuota(String boundary) {
- if (boundary.matches("\".*\"")) {
- boundary = boundary.replace("\"", "");
- }
- boundary = boundary.replaceAll("\\)", "\\\\)");
- boundary = boundary.replaceAll("\\(", "\\\\(");
- boundary = boundary.replaceAll("\\?", "\\\\?");
- boundary = boundary.replaceAll("\\+", "\\\\+");
- return boundary;
- }
-
- private List<String> getAcceptHeader(final Map<String, List<String>> headers) {
- List<String> acceptHeaders = new ArrayList<String>();
- List<String> requestAcceptHeaderList = headers.get(HttpHeaders.ACCEPT.toLowerCase(Locale.ENGLISH));
-
- if (requestAcceptHeaderList != null) {
- acceptHeaders = requestAcceptHeaderList;
- }
- return acceptHeaders;
- }
-
- private List<Locale> getAcceptLanguageHeader(final Map<String, List<String>> headers) {
- List<String> requestAcceptLanguageList = headers.get(HttpHeaders.ACCEPT_LANGUAGE.toLowerCase(Locale.ENGLISH));
- List<Locale> acceptLanguages = new ArrayList<Locale>();
- if (requestAcceptLanguageList != null) {
- for (String acceptLanguage : requestAcceptLanguageList) {
- String[] part = acceptLanguage.split("-");
- String language = part[0];
- String country = "";
- if (part.length == 2) {
- country = part[part.length - 1];
- }
- Locale locale = new Locale(language, country);
- acceptLanguages.add(locale);
- }
- }
- return acceptLanguages;
- }
-
- private String getContentTypeHeader(final Map<String, List<String>> headers) {
- List<String> requestContentTypeList = headers.get(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ENGLISH));
- String contentType = null;
- if (requestContentTypeList != null) {
- for (String requestContentType : requestContentTypeList) {
- contentType = contentType != null ? contentType + "," + requestContentType : requestContentType;
- }
- }
- return contentType;
- }
-}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseParser.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseParser.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseParser.java
deleted file mode 100644
index 239311b..0000000
--- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseParser.java
+++ /dev/null
@@ -1,356 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ******************************************************************************/
-package org.apache.olingo.odata2.core.batch;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Scanner;
-import java.util.regex.MatchResult;
-import java.util.regex.Pattern;
-
-import org.apache.olingo.odata2.api.batch.BatchException;
-import org.apache.olingo.odata2.api.client.batch.BatchSingleResponse;
-import org.apache.olingo.odata2.api.commons.HttpContentType;
-import org.apache.olingo.odata2.api.commons.HttpHeaders;
-import org.apache.olingo.odata2.core.exception.ODataRuntimeException;
-
-public class BatchResponseParser {
-
- private static final String CRLF = "\r\n";
- private static final String REG_EX_OPTIONAL_WHITESPACE = "\\s?";
- private static final String REG_EX_ZERO_OR_MORE_WHITESPACES = "\\s*";
- private static final String ANY_CHARACTERS = ".*";
-
- private static final Pattern REG_EX_BLANK_LINE = Pattern.compile("(|" + REG_EX_ZERO_OR_MORE_WHITESPACES + ")");
- private static final Pattern REG_EX_HEADER = Pattern.compile("([a-zA-Z\\-]+):" + REG_EX_OPTIONAL_WHITESPACE + "(.*)"
- + REG_EX_ZERO_OR_MORE_WHITESPACES);
- private static final Pattern REG_EX_VERSION = Pattern.compile("(?:HTTP/[0-9]\\.[0-9])");
- private static final Pattern REG_EX_ANY_BOUNDARY_STRING = Pattern.compile("--" + ANY_CHARACTERS
- + REG_EX_ZERO_OR_MORE_WHITESPACES);
- private static final Pattern REG_EX_STATUS_LINE = Pattern.compile(REG_EX_VERSION + "\\s" + "([0-9]{3})\\s([\\S ]+)"
- + REG_EX_ZERO_OR_MORE_WHITESPACES);
- private static final Pattern REG_EX_BOUNDARY_PARAMETER = Pattern.compile(REG_EX_OPTIONAL_WHITESPACE
- + "boundary=(\".*\"|.*)" + REG_EX_ZERO_OR_MORE_WHITESPACES);
- private static final Pattern REG_EX_CONTENT_TYPE = Pattern.compile(REG_EX_OPTIONAL_WHITESPACE
- + HttpContentType.MULTIPART_MIXED);
-
- private static final String REG_EX_BOUNDARY =
- "([a-zA-Z0-9_\\-\\.'\\+]{1,70})|\"([a-zA-Z0-9_\\-\\.'\\+ \\(\\)" +
- ",/:=\\?]{1,69}[a-zA-Z0-9_\\-\\.'\\+\\(\\),/:=\\?])\""; // See RFC 2046
-
- private String contentTypeMime;
- private String boundary;
- private String currentContentId;
- private int currentLineNumber = 0;
-
- public BatchResponseParser(final String contentType) {
- contentTypeMime = contentType;
- }
-
- public List<BatchSingleResponse> parse(final InputStream in) throws BatchException {
- Scanner scanner = new Scanner(in, BatchHelper.DEFAULT_ENCODING);
- scanner.useDelimiter(CRLF);
- List<BatchSingleResponse> responseList;
- try {
- responseList = Collections.unmodifiableList(parseBatchResponse(scanner));
- } finally {// NOPMD (suppress DoNotThrowExceptionInFinally)
- scanner.close();
- try {
- in.close();
- } catch (IOException e) {
- throw new ODataRuntimeException(e);
- }
- }
- return responseList;
- }
-
- private List<BatchSingleResponse> parseBatchResponse(final Scanner scanner) throws BatchException {
- List<BatchSingleResponse> responses = new ArrayList<BatchSingleResponse>();
- if (contentTypeMime != null) {
- boundary = getBoundary(contentTypeMime);
- parsePreamble(scanner);
- final String closeDelimiter = "--" + boundary + "--" + REG_EX_ZERO_OR_MORE_WHITESPACES;
- while (scanner.hasNext() && !scanner.hasNext(closeDelimiter)) {
- responses.addAll(parseMultipart(scanner, boundary, false));
- }
- if (scanner.hasNext(closeDelimiter)) {
- scanner.next(closeDelimiter);
- currentLineNumber++;
- } else {
- throw new BatchException(BatchException.MISSING_CLOSE_DELIMITER.addContent(currentLineNumber));
- }
- } else {
- throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
- }
- return responses;
-
- }
-
- // The method parses additional information prior to the first boundary delimiter line
- private void parsePreamble(final Scanner scanner) {
- while (scanner.hasNext() && !scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
- scanner.next();
- currentLineNumber++;
- }
- }
-
- private List<BatchSingleResponse> parseMultipart(final Scanner scanner, final String boundary,
- final boolean isChangeSet) throws BatchException {
- Map<String, String> mimeHeaders = new HashMap<String, String>();
- List<BatchSingleResponse> responses = new ArrayList<BatchSingleResponse>();
- if (scanner.hasNext("--" + boundary + REG_EX_ZERO_OR_MORE_WHITESPACES)) {
- scanner.next();
- currentLineNumber++;
- mimeHeaders = parseMimeHeaders(scanner);
- currentContentId = mimeHeaders.get(BatchHelper.HTTP_CONTENT_ID.toLowerCase(Locale.ENGLISH));
-
- final String contentType = mimeHeaders.get(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ENGLISH));
- if (contentType == null) {
- throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
- }
- if (isChangeSet) {
- if (HttpContentType.APPLICATION_HTTP.equalsIgnoreCase(contentType)) {
- validateEncoding(mimeHeaders.get(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING.toLowerCase(Locale.ENGLISH)));
- parseNewLine(scanner);// mandatory
- BatchSingleResponseImpl response = parseResponse(scanner, isChangeSet);
- responses.add(response);
- } else {
- throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.APPLICATION_HTTP));
- }
- } else {
- if (HttpContentType.APPLICATION_HTTP.equalsIgnoreCase(contentType)) {
- validateEncoding(mimeHeaders.get(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING.toLowerCase(Locale.ENGLISH)));
- parseNewLine(scanner);// mandatory
- BatchSingleResponseImpl response = parseResponse(scanner, isChangeSet);
- responses.add(response);
- } else if (contentType.matches(REG_EX_OPTIONAL_WHITESPACE + HttpContentType.MULTIPART_MIXED + ANY_CHARACTERS)) {
- String changeSetBoundary = getBoundary(contentType);
- if (boundary.equals(changeSetBoundary)) {
- throw new BatchException(BatchException.INVALID_CHANGESET_BOUNDARY.addContent(currentLineNumber));
- }
- parseNewLine(scanner);// mandatory
- Pattern changeSetCloseDelimiter =
- Pattern.compile("--" + changeSetBoundary + "--" + REG_EX_ZERO_OR_MORE_WHITESPACES);
- while (!scanner.hasNext(changeSetCloseDelimiter)) {
- responses.addAll(parseMultipart(scanner, changeSetBoundary, true));
- }
- scanner.next(changeSetCloseDelimiter);
- currentLineNumber++;
- parseOptionalEmptyLine(scanner);
- } else {
- throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED
- + " or " + HttpContentType.APPLICATION_HTTP));
- }
- }
- } else if (scanner.hasNext(boundary + REG_EX_ZERO_OR_MORE_WHITESPACES)) {
- currentLineNumber++;
- throw new BatchException(BatchException.INVALID_BOUNDARY_DELIMITER.addContent(currentLineNumber));
- } else if (scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
- currentLineNumber++;
- throw new BatchException(BatchException.NO_MATCH_WITH_BOUNDARY_STRING.addContent(boundary).addContent(
- currentLineNumber));
- } else {
- currentLineNumber++;
- throw new BatchException(BatchException.MISSING_BOUNDARY_DELIMITER.addContent(currentLineNumber));
- }
- return responses;
-
- }
-
- private BatchSingleResponseImpl parseResponse(final Scanner scanner, final boolean isChangeSet)
- throws BatchException {
- BatchSingleResponseImpl response = new BatchSingleResponseImpl();
- if (scanner.hasNext(REG_EX_STATUS_LINE)) {
- scanner.next(REG_EX_STATUS_LINE);
- currentLineNumber++;
- final String statusCode;
- final String statusInfo;
- MatchResult result = scanner.match();
- if (result.groupCount() == 2) {
- statusCode = result.group(1);
- statusInfo = result.group(2);
- } else {
- currentLineNumber++;
- throw new BatchException(BatchException.INVALID_STATUS_LINE.addContent(scanner.next()).addContent(
- currentLineNumber));
- }
-
- Map<String, String> headers = parseResponseHeaders(scanner);
- parseNewLine(scanner);
- String body = parseBody(scanner);
- String contentLengthHeader = getHeaderValue(headers, HttpHeaders.CONTENT_LENGTH);
- if (contentLengthHeader != null) {
- int contentLength = Integer.parseInt(contentLengthHeader);
- if (contentLength < body.length()) {
- body = body.substring(0, contentLength);
- }
- }
- response.setStatusCode(statusCode);
- response.setStatusInfo(statusInfo);
- response.setHeaders(headers);
- response.setContentId(currentContentId);
- response.setBody(body);
- } else {
- currentLineNumber++;
- throw new BatchException(BatchException.INVALID_STATUS_LINE.addContent(scanner.next()).addContent(
- currentLineNumber));
- }
- return response;
- }
-
- private void validateEncoding(final String encoding) throws BatchException {
- if (!BatchHelper.BINARY_ENCODING.equalsIgnoreCase(encoding)) {
- throw new BatchException(BatchException.INVALID_CONTENT_TRANSFER_ENCODING);
- }
- }
-
- private Map<String, String> parseMimeHeaders(final Scanner scanner) throws BatchException {
- Map<String, String> headers = new HashMap<String, String>();
- while (scanner.hasNext() && !(scanner.hasNext(REG_EX_BLANK_LINE))) {
- if (scanner.hasNext(REG_EX_HEADER)) {
- scanner.next(REG_EX_HEADER);
- currentLineNumber++;
- MatchResult result = scanner.match();
- if (result.groupCount() == 2) {
- String headerName = result.group(1).trim().toLowerCase(Locale.ENGLISH);
- String headerValue = result.group(2).trim();
- headers.put(headerName, headerValue);
- }
- } else {
- throw new BatchException(BatchException.INVALID_HEADER.addContent(scanner.next()));
- }
- }
- return headers;
- }
-
- private Map<String, String> parseResponseHeaders(final Scanner scanner) throws BatchException {
- Map<String, String> headers = new HashMap<String, String>();
- while (scanner.hasNext() && !scanner.hasNext(REG_EX_BLANK_LINE)) {
- if (scanner.hasNext(REG_EX_HEADER)) {
- scanner.next(REG_EX_HEADER);
- currentLineNumber++;
- MatchResult result = scanner.match();
- if (result.groupCount() == 2) {
- String headerName = result.group(1).trim();
- String headerValue = result.group(2).trim();
- if (BatchHelper.HTTP_CONTENT_ID.equalsIgnoreCase(headerName)) {
- if (currentContentId == null) {
- currentContentId = headerValue;
- }
- } else {
- headers.put(headerName, headerValue);
- }
- }
- } else {
- currentLineNumber++;
- throw new BatchException(BatchException.INVALID_HEADER.addContent(scanner.next())
- .addContent(currentLineNumber));
- }
- }
- return headers;
- }
-
- private String getHeaderValue(final Map<String, String> headers, final String headerName) {
- for (Map.Entry<String, String> header : headers.entrySet()) {
- if (headerName.equalsIgnoreCase(header.getKey())) {
- return header.getValue();
- }
- }
- return null;
- }
-
- private String parseBody(final Scanner scanner) {
- StringBuilder body = null;
- while (scanner.hasNext() && !scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
- if (body == null) {
- body = new StringBuilder(scanner.next());
- } else {
- body.append(CRLF).append(scanner.next());
- }
- currentLineNumber++;
- }
- String responseBody = body != null ? body.toString() : null;
- return responseBody;
- }
-
- private String getBoundary(final String contentType) throws BatchException {
- Scanner contentTypeScanner = new Scanner(contentType);
- contentTypeScanner.useDelimiter(";\\s?");
- if (contentTypeScanner.hasNext(REG_EX_CONTENT_TYPE)) {
- contentTypeScanner.next(REG_EX_CONTENT_TYPE);
- } else {
- contentTypeScanner.close();
- throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED));
- }
- if (contentTypeScanner.hasNext(REG_EX_BOUNDARY_PARAMETER)) {
- contentTypeScanner.next(REG_EX_BOUNDARY_PARAMETER);
- MatchResult result = contentTypeScanner.match();
- contentTypeScanner.close();
- if (result.groupCount() == 1 && result.group(1).trim().matches(REG_EX_BOUNDARY)) {
- return trimQuota(result.group(1).trim());
- } else {
- throw new BatchException(BatchException.INVALID_BOUNDARY);
- }
- } else {
- contentTypeScanner.close();
- throw new BatchException(BatchException.MISSING_PARAMETER_IN_CONTENT_TYPE);
- }
- }
-
- private void parseNewLine(final Scanner scanner) throws BatchException {
- if (scanner.hasNext() && scanner.hasNext(REG_EX_BLANK_LINE)) {
- scanner.next();
- currentLineNumber++;
- } else {
- currentLineNumber++;
- if (scanner.hasNext()) {
- throw new BatchException(BatchException.MISSING_BLANK_LINE.addContent(scanner.next()).addContent(
- currentLineNumber));
- } else {
- throw new BatchException(BatchException.TRUNCATED_BODY.addContent(currentLineNumber));
-
- }
- }
- }
-
- private void parseOptionalEmptyLine(final Scanner scanner) {
- if (scanner.hasNext() && scanner.hasNext(REG_EX_BLANK_LINE)) {
- scanner.next();
- currentLineNumber++;
- }
- }
-
- private String trimQuota(String boundary) {
- if (boundary.matches("\".*\"")) {
- boundary = boundary.replace("\"", "");
- }
- boundary = boundary.replaceAll("\\)", "\\\\)");
- boundary = boundary.replaceAll("\\(", "\\\\(");
- boundary = boundary.replaceAll("\\?", "\\\\?");
- boundary = boundary.replaceAll("\\+", "\\\\+");
- return boundary;
- }
-
-}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchBodyPart.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchBodyPart.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchBodyPart.java
new file mode 100644
index 0000000..e355f84
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchBodyPart.java
@@ -0,0 +1,155 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.commons.HttpContentType;
+import org.apache.olingo.odata2.api.commons.HttpHeaders;
+import org.apache.olingo.odata2.core.batch.v2.BatchParserCommon.HeaderField;
+import org.apache.olingo.odata2.core.exception.ODataRuntimeException;
+
+public class BatchBodyPart implements BatchPart {
+ final private Map<String, HeaderField> headers;
+ final private String boundary;
+ final private boolean isChangeSet;
+ final private boolean isStrict;
+ final private List<String> body;
+ private boolean isParsed = false;
+ private List<BatchQueryOperation> requests;
+
+ public BatchBodyPart(final List<String> bodyPartMessage, final String boundary, final boolean isStrict)
+ throws BatchException {
+ this.boundary = boundary;
+ this.isStrict = isStrict;
+
+ List<String> remainingMessage = new LinkedList<String>();
+ remainingMessage.addAll(bodyPartMessage);
+
+ headers = BatchParserCommon.consumeHeaders(remainingMessage);
+ BatchParserCommon.consumeBlankLine(remainingMessage, isStrict);
+ isChangeSet = isChangeSet(headers);
+ body = remainingMessage;
+ }
+
+ public BatchBodyPart parse(final int contentLength) throws BatchException {
+ List<String> remainingMessage = BatchParserCommon.trimStringListToLength(body, contentLength);
+ requests = consumeRequest(remainingMessage);
+ isParsed = true;
+
+ return this;
+ }
+
+ private boolean isChangeSet(final Map<String, HeaderField> headers) throws BatchException {
+ final HeaderField contentTypeField = headers.get(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ENGLISH));
+ boolean isChangeSet = false;
+
+ if (contentTypeField == null || contentTypeField.getValues().size() == 0) {
+ throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
+ }
+
+ for (String contentType : contentTypeField.getValues()) {
+ if (isContentTypeMultiPartMixed(contentType)) {
+ isChangeSet = true;
+ }
+ }
+
+ return isChangeSet;
+ }
+
+ private boolean isContentTypeMultiPartMixed(final String contentType) {
+ return contentType.contains(HttpContentType.MULTIPART_MIXED);
+ }
+
+ private List<BatchQueryOperation> consumeRequest(final List<String> remainingMessage) throws BatchException {
+ if (isChangeSet) {
+ return consumeChangeSet(remainingMessage);
+ } else {
+ return consumeQueryOperation(remainingMessage);
+ }
+ }
+
+ private List<BatchQueryOperation> consumeChangeSet(final List<String> remainingMessage)
+ throws BatchException {
+ final List<List<String>> changeRequests = splitChangeSet(remainingMessage);
+ final List<BatchQueryOperation> requestList = new LinkedList<BatchQueryOperation>();
+
+ for (List<String> changeRequest : changeRequests) {
+ requestList.add(new BatchChangeSet(changeRequest, isStrict).parse());
+ }
+
+ return requestList;
+ }
+
+ private List<List<String>> splitChangeSet(final List<String> remainingMessage)
+ throws BatchException {
+
+ final String changeSetBoundary = BatchParserCommon.getBoundary(getContentType());
+ validateChangeSetBoundary(changeSetBoundary);
+
+ return BatchParserCommon.splitMessageByBoundary(remainingMessage, changeSetBoundary);
+ }
+
+ private List<BatchQueryOperation> consumeQueryOperation(final List<String> remainingMessage)
+ throws BatchException {
+ final List<BatchQueryOperation> requestList = new LinkedList<BatchQueryOperation>();
+ requestList.add(new BatchQueryOperation(remainingMessage, isStrict).parse());
+
+ return requestList;
+ }
+
+ private void validateChangeSetBoundary(final String changeSetBoundary) throws BatchException {
+ if (changeSetBoundary.equals(boundary)) {
+ throw new BatchException(BatchException.INVALID_BOUNDARY);
+ }
+ }
+
+ private String getContentType() {
+ HeaderField contentTypeField = headers.get(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ENGLISH));
+
+ return (contentTypeField != null && contentTypeField.getValues().size() > 0) ? contentTypeField.getValues().get(0)
+ : "";
+ }
+
+ @Override
+ public Map<String, HeaderField> getHeaders() {
+ return headers;
+ }
+
+ @Override
+ public boolean isStrict() {
+ return isStrict;
+ }
+
+ public boolean isChangeSet() {
+ return isChangeSet;
+ }
+
+ public List<BatchQueryOperation> getRequests() {
+ if (!isParsed) {
+ throw new ODataRuntimeException("Batch part must be parsed first");
+ }
+
+ return requests;
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchChangeSet.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchChangeSet.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchChangeSet.java
new file mode 100644
index 0000000..5331ff8
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchChangeSet.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+import java.util.List;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+
+public class BatchChangeSet extends BatchQueryOperation {
+ private BatchQueryOperation request;
+
+ public BatchChangeSet(final List<String> message, final boolean isStrict) throws BatchException {
+ super(message, isStrict);
+ }
+
+ @Override
+ public BatchChangeSet parse() throws BatchException {
+ headers = BatchParserCommon.consumeHeaders(message);
+ BatchParserCommon.consumeBlankLine(message, isStrict);
+
+ request = new BatchQueryOperation(message, isStrict).parse();
+
+ return this;
+ }
+
+ public BatchQueryOperation getRequest() {
+ return request;
+ }
+
+ @Override
+ public List<String> getBody() {
+ return request.getBody();
+ }
+
+ @Override
+ public String getHttpMethod() {
+ return request.getHttpMethod();
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParser.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParser.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParser.java
new file mode 100644
index 0000000..b64453b
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParser.java
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.batch.BatchParserResult;
+import org.apache.olingo.odata2.api.batch.BatchRequestPart;
+import org.apache.olingo.odata2.api.client.batch.BatchSingleResponse;
+import org.apache.olingo.odata2.api.ep.EntityProviderBatchProperties;
+import org.apache.olingo.odata2.api.uri.PathInfo;
+import org.apache.olingo.odata2.api.uri.PathSegment;
+import org.apache.olingo.odata2.core.exception.ODataRuntimeException;
+
+public class BatchParser {
+
+ private final PathInfo batchRequestPathInfo;
+ private final String contentTypeMime;
+ private final boolean isStrict;
+
+ public BatchParser(final String contentType, final boolean isStrict) {
+ this(contentType, null, isStrict);
+ }
+
+ public BatchParser(final String contentType, final EntityProviderBatchProperties properties, final boolean isStrict) {
+ contentTypeMime = contentType;
+ batchRequestPathInfo = (properties != null) ? properties.getPathInfo() : null;
+ this.isStrict = isStrict;
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<BatchSingleResponse> parseBatchResponse(final InputStream in) throws BatchException {
+ return (List<BatchSingleResponse>) parse(in, new BatchResponseTransformator());
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<BatchRequestPart> parseBatchRequest(final InputStream in) throws BatchException {
+ return (List<BatchRequestPart>) parse(in, new BatchRequestTransformator());
+ }
+
+ private List<? extends BatchParserResult> parse(final InputStream in, final BatchTransformator transformator)
+ throws BatchException {
+ try {
+ return parseBatch(in, transformator);
+ } catch (IOException e) {
+ throw new ODataRuntimeException(e);
+ } finally {
+ try {
+ in.close();
+ } catch (IOException e) {
+ throw new ODataRuntimeException(e);
+ }
+ }
+ }
+
+ private List<BatchParserResult> parseBatch(final InputStream in,
+ final BatchTransformator transformator) throws BatchException, IOException {
+
+ final String baseUri = getBaseUri();
+ final String boundary = BatchParserCommon.getBoundary(contentTypeMime);
+ final List<BatchParserResult> resultList = new LinkedList<BatchParserResult>();
+ final List<List<String>> bodyPartStrings = splitBodyParts(in, boundary);
+
+ for (List<String> bodyPartString : bodyPartStrings) {
+ BatchBodyPart bodyPart = new BatchBodyPart(bodyPartString, boundary, isStrict);
+ resultList.addAll(transformator.transform(bodyPart, batchRequestPathInfo, baseUri));
+ }
+
+ return resultList;
+ }
+
+ private List<List<String>> splitBodyParts(final InputStream in, final String boundary)
+ throws IOException, BatchException {
+
+ final BufferedReaderIncludingLineEndings reader = new BufferedReaderIncludingLineEndings(new InputStreamReader(in));
+ final List<String> message = reader.toList();
+ reader.close();
+
+ return BatchParserCommon.splitMessageByBoundary(message, boundary);
+ }
+
+ private String getBaseUri() throws BatchException {
+ String baseUri = "";
+
+ if (batchRequestPathInfo != null && batchRequestPathInfo.getServiceRoot() != null) {
+ final String uri = batchRequestPathInfo.getServiceRoot().toASCIIString();
+
+ baseUri = addPathSegements(removeLastSlash(uri));
+ }
+
+ return baseUri;
+ }
+
+ private String addPathSegements(String baseUri) {
+ for (PathSegment precedingPS : batchRequestPathInfo.getPrecedingSegments()) {
+ baseUri = baseUri + "/" + precedingPS.getPath();
+ }
+
+ return baseUri;
+ }
+
+ private String removeLastSlash(String baseUri) {
+ if (baseUri.lastIndexOf('/') == baseUri.length() - 1) {
+ baseUri = baseUri.substring(0, baseUri.length() - 1);
+ }
+
+ return baseUri;
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParserCommon.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParserCommon.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParserCommon.java
new file mode 100644
index 0000000..51314dd
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParserCommon.java
@@ -0,0 +1,414 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.commons.HttpContentType;
+import org.apache.olingo.odata2.api.commons.HttpHeaders;
+import org.apache.olingo.odata2.api.uri.PathInfo;
+import org.apache.olingo.odata2.api.uri.PathSegment;
+import org.apache.olingo.odata2.core.ODataPathSegmentImpl;
+import org.apache.olingo.odata2.core.PathInfoImpl;
+import org.apache.olingo.odata2.core.batch.AcceptParser;
+import org.apache.olingo.odata2.core.commons.Decoder;
+
+public class BatchParserCommon {
+ private static final String BOUNDARY_IDENTIFIER = "boundary=";
+ private static final String REG_EX_BOUNDARY =
+ "([a-zA-Z0-9_\\-\\.'\\+]{1,70})|\"([a-zA-Z0-9_\\-\\.'\\+\\s\\" +
+ "(\\),/:=\\?]{1,69}[a-zA-Z0-9_\\-\\.'\\+\\(\\),/:=\\?])\""; // See RFC 2046
+
+ private static final Pattern REG_EX_HEADER = Pattern.compile("([a-zA-Z\\-]+):\\s?(.*)\\s*");
+
+ public static List<String> trimStringListToLength(final List<String> list, final int length) {
+ final Iterator<String> iter = list.iterator();
+ final List<String> result = new ArrayList<String>();
+ boolean isEndReached = false;
+ int currentLength = 0;
+
+ while (!isEndReached && iter.hasNext()) {
+ String currentLine = iter.next();
+
+ if (currentLength + currentLine.length() <= length) {
+ result.add(currentLine);
+ currentLength += currentLine.length();
+ } else {
+ result.add(currentLine.substring(0, length - currentLength));
+ isEndReached = true;
+ }
+ }
+
+ return result;
+ }
+
+ public static String stringListToString(final List<String> list) {
+ StringBuilder builder = new StringBuilder();
+
+ for (String currentLine : list) {
+ builder.append(currentLine);
+ }
+
+ return builder.toString();
+ }
+
+ public static InputStream convertMessageToInputStream(final List<String> message, final int contentLength)
+ throws BatchException {
+ List<String> shortenedMessage = BatchParserCommon.trimStringListToLength(message, contentLength);
+
+ return new ByteArrayInputStream(BatchParserCommon.stringListToString(shortenedMessage).getBytes());
+ }
+
+ static List<List<String>> splitMessageByBoundary(final List<String> message, final String boundary)
+ throws BatchException {
+ final List<List<String>> messageParts = new LinkedList<List<String>>();
+ List<String> currentPart = new ArrayList<String>();
+ boolean isEndReached = false;
+
+ for (String currentLine : message) {
+ if (currentLine.contains("--" + boundary + "--")) {
+ removeEndingCRLFFromList(currentPart);
+ messageParts.add(currentPart);
+ isEndReached = true;
+ } else if (currentLine.contains("--" + boundary)) {
+ removeEndingCRLFFromList(currentPart);
+ messageParts.add(currentPart);
+ currentPart = new LinkedList<String>();
+ } else {
+ currentPart.add(currentLine);
+ }
+
+ if (isEndReached) {
+ break;
+ }
+ }
+
+ // Remove preamble
+ if (messageParts.size() > 0) {
+ messageParts.remove(0);
+ } else {
+ throw new BatchException(BatchException.MISSING_BOUNDARY_DELIMITER);
+ }
+
+ if (messageParts.size() == 0) {
+ throw new BatchException(BatchException.NO_MATCH_WITH_BOUNDARY_STRING);
+ }
+
+ if (!isEndReached) {
+ throw new BatchException(BatchException.MISSING_CLOSE_DELIMITER);
+ }
+
+ return messageParts;
+ }
+
+ private static void removeEndingCRLFFromList(final List<String> list) {
+ if (list.size() > 0) {
+ String lastLine = list.remove(list.size() - 1);
+ list.add(removeEndingCRLF(lastLine));
+ }
+ }
+
+ public static String removeEndingCRLF(final String line) {
+ Pattern pattern = Pattern.compile("(.*)(\r\n){1}( *)", Pattern.DOTALL);
+ Matcher matcher = pattern.matcher(line);
+
+ if (matcher.matches()) {
+ return matcher.group(1);
+ } else {
+ return line;
+ }
+ }
+
+ static Map<String, HeaderField> consumeHeaders(final List<String> remainingMessage) throws BatchException {
+ final Map<String, HeaderField> headers = new HashMap<String, HeaderField>();
+ boolean isHeader = true;
+ String currentLine;
+ Iterator<String> iter = remainingMessage.iterator();
+
+ while (iter.hasNext() && isHeader) {
+ currentLine = iter.next();
+ Matcher headerMatcher = REG_EX_HEADER.matcher(currentLine);
+
+ if (headerMatcher.matches() && headerMatcher.groupCount() == 2) {
+ iter.remove();
+
+ String headerName = headerMatcher.group(1).trim();
+ String headerNameLowerCase = headerName.toLowerCase(Locale.ENGLISH);
+ String headerValue = headerMatcher.group(2).trim();
+
+ if (HttpHeaders.ACCEPT.equalsIgnoreCase(headerNameLowerCase)) {
+ List<String> acceptHeaders = AcceptParser.parseAcceptHeaders(headerValue);
+ headers.put(headerNameLowerCase, new HeaderField(headerName, acceptHeaders));
+ } else if (HttpHeaders.ACCEPT_LANGUAGE.equalsIgnoreCase(headerNameLowerCase)) {
+ List<String> acceptLanguageHeaders = AcceptParser.parseAcceptableLanguages(headerValue);
+ headers.put(headerNameLowerCase, new HeaderField(headerName, acceptLanguageHeaders));
+ } else {
+ HeaderField headerField = headers.get(headerNameLowerCase);
+ headerField = headerField == null ? new HeaderField(headerName) : headerField;
+ headers.put(headerNameLowerCase, headerField);
+ headerField.getValues().add(headerValue);
+ }
+ } else {
+ isHeader = false;
+ }
+ }
+
+ return Collections.unmodifiableMap(headers);
+ }
+
+ static void consumeBlankLine(final List<String> remainingMessage, final boolean isStrict) throws BatchException {
+ if (remainingMessage.size() > 0 && "".equals(remainingMessage.get(0).trim())) {
+ remainingMessage.remove(0);
+ } else {
+ if (isStrict) {
+ throw new BatchException(BatchException.MISSING_BLANK_LINE);
+ }
+ }
+ }
+
+ static void consumeLastBlankLine(final List<String> message, final boolean isStrict) throws BatchException {
+ if (message.size() > 0 && "".equals(message.get(message.size() - 1).trim())) {
+ message.remove(message.size() - 1);
+ } else {
+ if (isStrict) {
+ throw new BatchException(BatchException.MISSING_BLANK_LINE);
+ }
+ }
+ }
+
+ static String getBoundary(final String contentType) throws BatchException {
+ if (contentType.contains(HttpContentType.MULTIPART_MIXED)) {
+ String[] parts = contentType.split(BOUNDARY_IDENTIFIER);
+
+ if (parts.length == 2) {
+ if (parts[1].matches(REG_EX_BOUNDARY)) {
+ return trimQuota(parts[1].trim());
+ } else {
+ throw new BatchException(BatchException.INVALID_BOUNDARY);
+ }
+ } else {
+ throw new BatchException(BatchException.MISSING_PARAMETER_IN_CONTENT_TYPE);
+ }
+ } else {
+ throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED));
+ }
+ }
+
+ static Map<String, List<String>> parseQueryParameter(final String httpRequest) {
+ Map<String, List<String>> queryParameter = new HashMap<String, List<String>>();
+
+ String[] requestParts = httpRequest.split(" ");
+ if (requestParts.length == 3) {
+
+ String[] parts = requestParts[1].split("\\?");
+ if (parts.length == 2) {
+ String[] parameters = parts[1].split("&");
+
+ for (String parameter : parameters) {
+ String[] parameterParts = parameter.split("=");
+ String parameterName = parameterParts[0].toLowerCase(Locale.ENGLISH);
+
+ if (parameterParts.length == 2) {
+ List<String> valueList = queryParameter.get(parameterName);
+ valueList = valueList == null ? new LinkedList<String>() : valueList;
+ queryParameter.put(parameterName, valueList);
+
+ String[] valueParts = parameterParts[1].split(",");
+ for (String value : valueParts) {
+ valueList.add(Decoder.decode(value));
+ }
+ }
+ }
+ }
+ }
+
+ return queryParameter;
+ }
+
+ public static PathInfo parseRequestUri(final String httpRequest, final PathInfo batchRequestPathInfo,
+ final String baseUri)
+ throws BatchException {
+
+ final String odataPathSegmentsAsString;
+ final String queryParametersAsString;
+
+ PathInfoImpl pathInfo = new PathInfoImpl();
+ pathInfo.setServiceRoot(batchRequestPathInfo.getServiceRoot());
+ pathInfo.setPrecedingPathSegment(batchRequestPathInfo.getPrecedingSegments());
+
+ String[] requestParts = httpRequest.split(" ");
+ if (requestParts.length == 3) {
+ String uri = requestParts[1];
+ Pattern regexRequestUri;
+
+ try {
+ URI uriObject = new URI(uri);
+ if (uriObject.isAbsolute()) {
+ regexRequestUri = Pattern.compile(baseUri + "/([^/][^?]*)(\\?.*)?");
+ } else {
+ regexRequestUri = Pattern.compile("([^/][^?]*)(\\?.*)?");
+
+ }
+
+ Matcher uriParts = regexRequestUri.matcher(uri);
+
+ if (uriParts.lookingAt() && uriParts.groupCount() == 2) {
+ odataPathSegmentsAsString = uriParts.group(1);
+ queryParametersAsString = uriParts.group(2) != null ? uriParts.group(2) : "";
+
+ pathInfo.setODataPathSegment(parseODataPathSegments(odataPathSegmentsAsString));
+ if (!odataPathSegmentsAsString.startsWith("$")) {
+ String requestUri = baseUri + "/" + odataPathSegmentsAsString + queryParametersAsString;
+ pathInfo.setRequestUri(new URI(requestUri));
+ }
+
+ } else {
+ throw new BatchException(BatchException.INVALID_URI);
+ }
+
+ } catch (URISyntaxException e) {
+ throw new BatchException(BatchException.INVALID_URI, e);
+ }
+ } else {
+ throw new BatchException(BatchException.INVALID_REQUEST_LINE);
+ }
+
+ return pathInfo;
+ }
+
+ public static List<PathSegment> parseODataPathSegments(final String odataPathSegmentsAsString) {
+ final List<PathSegment> odataPathSegments = new ArrayList<PathSegment>();
+ final String[] pathParts = odataPathSegmentsAsString.split("/");
+
+ for (final String pathSegment : pathParts) {
+ odataPathSegments.add(new ODataPathSegmentImpl(pathSegment, null));
+ }
+
+ return odataPathSegments;
+ }
+
+ private static String trimQuota(String boundary) {
+ if (boundary.matches("\".*\"")) {
+ boundary = boundary.replace("\"", "");
+ }
+
+ return boundary;
+ }
+
+ public static Map<String, String> headerFieldMapToSingleMap(final Map<String, HeaderField> headers) {
+ final Map<String, String> singleMap = new HashMap<String, String>();
+
+ for (final String key : headers.keySet()) {
+ HeaderField field = headers.get(key);
+ String value = field.getValues().size() > 0 ? field.getValues().get(0) : "";
+ singleMap.put(field.getFieldName(), value);
+ }
+
+ return singleMap;
+ }
+
+ public static Map<String, List<String>> headerFieldMapToMultiMap(final Map<String, HeaderField> headers) {
+ final Map<String, List<String>> singleMap = new HashMap<String, List<String>>();
+
+ for (final String key : headers.keySet()) {
+ HeaderField field = headers.get(key);
+ singleMap.put(field.getFieldName(), field.getValues());
+ }
+
+ return singleMap;
+ }
+
+ public static class HeaderField implements Cloneable {
+ private String fieldName;
+ private List<String> values;
+
+ public HeaderField(final String fieldName) {
+ this(fieldName, new ArrayList<String>());
+ }
+
+ public HeaderField(final String fieldName, final List<String> values) {
+ this.fieldName = fieldName;
+ this.values = values;
+ }
+
+ public String getFieldName() {
+ return fieldName;
+ }
+
+ public List<String> getValues() {
+ return values;
+ }
+
+ public void setValues(final List<String> values) {
+ this.values = values;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((fieldName == null) ? 0 : fieldName.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ HeaderField other = (HeaderField) obj;
+ if (fieldName == null) {
+ if (other.fieldName != null) {
+ return false;
+ }
+ } else if (!fieldName.equals(other.fieldName)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public HeaderField clone() {
+ List<String> newValues = new ArrayList<String>();
+ newValues.addAll(values);
+
+ return new HeaderField(fieldName, newValues);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchPart.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchPart.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchPart.java
new file mode 100644
index 0000000..258f48a
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchPart.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+import java.util.Map;
+
+import org.apache.olingo.odata2.core.batch.v2.BatchParserCommon.HeaderField;
+
+public interface BatchPart {
+ public Map<String, HeaderField> getHeaders();
+
+ public boolean isStrict();
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/6eca235e/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchQueryOperation.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchQueryOperation.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchQueryOperation.java
new file mode 100644
index 0000000..179fffb
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchQueryOperation.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.core.batch.v2.BatchParserCommon.HeaderField;
+
+public class BatchQueryOperation implements BatchPart {
+
+ protected final boolean isStrict;
+ protected String httpMethod;
+ protected Map<String, HeaderField> headers;
+ protected List<String> body;
+ protected int bodySize;
+ protected List<String> message;
+
+ public BatchQueryOperation(final List<String> message, final boolean isStrict) {
+ this.isStrict = isStrict;
+ this.message = message;
+ }
+
+ public BatchQueryOperation parse() throws BatchException {
+ httpMethod = consumeHttpMethod(message);
+ headers = BatchParserCommon.consumeHeaders(message);
+ BatchParserCommon.consumeBlankLine(message, isStrict);
+ body = message;
+
+ return this;
+ }
+
+ protected String consumeHttpMethod(final List<String> message) throws BatchException {
+ if (message.size() > 0 && !message.get(0).trim().equals("")) {
+ String method = message.get(0);
+ message.remove(0);
+
+ return method;
+ } else {
+ throw new BatchException(BatchException.INVALID_QUERY_OPERATION_METHOD);
+ }
+ }
+
+ public String getHttpMethod() {
+ return httpMethod;
+ }
+
+ public List<String> getBody() {
+ return body;
+ }
+
+ public int getBodySize() {
+ return bodySize;
+ }
+
+ @Override
+ public Map<String, HeaderField> getHeaders() {
+ return headers;
+ }
+
+ @Override
+ public boolean isStrict() {
+ return isStrict;
+ }
+}