You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by mi...@apache.org on 2014/10/14 20:16:18 UTC
[05/13] [OLINGO-129] Merge with current master
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseParser.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseParser.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseParser.java
deleted file mode 100644
index 239311b..0000000
--- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/BatchResponseParser.java
+++ /dev/null
@@ -1,356 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- ******************************************************************************/
-package org.apache.olingo.odata2.core.batch;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Scanner;
-import java.util.regex.MatchResult;
-import java.util.regex.Pattern;
-
-import org.apache.olingo.odata2.api.batch.BatchException;
-import org.apache.olingo.odata2.api.client.batch.BatchSingleResponse;
-import org.apache.olingo.odata2.api.commons.HttpContentType;
-import org.apache.olingo.odata2.api.commons.HttpHeaders;
-import org.apache.olingo.odata2.core.exception.ODataRuntimeException;
-
-public class BatchResponseParser {
-
- private static final String CRLF = "\r\n";
- private static final String REG_EX_OPTIONAL_WHITESPACE = "\\s?";
- private static final String REG_EX_ZERO_OR_MORE_WHITESPACES = "\\s*";
- private static final String ANY_CHARACTERS = ".*";
-
- private static final Pattern REG_EX_BLANK_LINE = Pattern.compile("(|" + REG_EX_ZERO_OR_MORE_WHITESPACES + ")");
- private static final Pattern REG_EX_HEADER = Pattern.compile("([a-zA-Z\\-]+):" + REG_EX_OPTIONAL_WHITESPACE + "(.*)"
- + REG_EX_ZERO_OR_MORE_WHITESPACES);
- private static final Pattern REG_EX_VERSION = Pattern.compile("(?:HTTP/[0-9]\\.[0-9])");
- private static final Pattern REG_EX_ANY_BOUNDARY_STRING = Pattern.compile("--" + ANY_CHARACTERS
- + REG_EX_ZERO_OR_MORE_WHITESPACES);
- private static final Pattern REG_EX_STATUS_LINE = Pattern.compile(REG_EX_VERSION + "\\s" + "([0-9]{3})\\s([\\S ]+)"
- + REG_EX_ZERO_OR_MORE_WHITESPACES);
- private static final Pattern REG_EX_BOUNDARY_PARAMETER = Pattern.compile(REG_EX_OPTIONAL_WHITESPACE
- + "boundary=(\".*\"|.*)" + REG_EX_ZERO_OR_MORE_WHITESPACES);
- private static final Pattern REG_EX_CONTENT_TYPE = Pattern.compile(REG_EX_OPTIONAL_WHITESPACE
- + HttpContentType.MULTIPART_MIXED);
-
- private static final String REG_EX_BOUNDARY =
- "([a-zA-Z0-9_\\-\\.'\\+]{1,70})|\"([a-zA-Z0-9_\\-\\.'\\+ \\(\\)" +
- ",/:=\\?]{1,69}[a-zA-Z0-9_\\-\\.'\\+\\(\\),/:=\\?])\""; // See RFC 2046
-
- private String contentTypeMime;
- private String boundary;
- private String currentContentId;
- private int currentLineNumber = 0;
-
- public BatchResponseParser(final String contentType) {
- contentTypeMime = contentType;
- }
-
- public List<BatchSingleResponse> parse(final InputStream in) throws BatchException {
- Scanner scanner = new Scanner(in, BatchHelper.DEFAULT_ENCODING);
- scanner.useDelimiter(CRLF);
- List<BatchSingleResponse> responseList;
- try {
- responseList = Collections.unmodifiableList(parseBatchResponse(scanner));
- } finally {// NOPMD (suppress DoNotThrowExceptionInFinally)
- scanner.close();
- try {
- in.close();
- } catch (IOException e) {
- throw new ODataRuntimeException(e);
- }
- }
- return responseList;
- }
-
- private List<BatchSingleResponse> parseBatchResponse(final Scanner scanner) throws BatchException {
- List<BatchSingleResponse> responses = new ArrayList<BatchSingleResponse>();
- if (contentTypeMime != null) {
- boundary = getBoundary(contentTypeMime);
- parsePreamble(scanner);
- final String closeDelimiter = "--" + boundary + "--" + REG_EX_ZERO_OR_MORE_WHITESPACES;
- while (scanner.hasNext() && !scanner.hasNext(closeDelimiter)) {
- responses.addAll(parseMultipart(scanner, boundary, false));
- }
- if (scanner.hasNext(closeDelimiter)) {
- scanner.next(closeDelimiter);
- currentLineNumber++;
- } else {
- throw new BatchException(BatchException.MISSING_CLOSE_DELIMITER.addContent(currentLineNumber));
- }
- } else {
- throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
- }
- return responses;
-
- }
-
- // The method parses additional information prior to the first boundary delimiter line
- private void parsePreamble(final Scanner scanner) {
- while (scanner.hasNext() && !scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
- scanner.next();
- currentLineNumber++;
- }
- }
-
- private List<BatchSingleResponse> parseMultipart(final Scanner scanner, final String boundary,
- final boolean isChangeSet) throws BatchException {
- Map<String, String> mimeHeaders = new HashMap<String, String>();
- List<BatchSingleResponse> responses = new ArrayList<BatchSingleResponse>();
- if (scanner.hasNext("--" + boundary + REG_EX_ZERO_OR_MORE_WHITESPACES)) {
- scanner.next();
- currentLineNumber++;
- mimeHeaders = parseMimeHeaders(scanner);
- currentContentId = mimeHeaders.get(BatchHelper.HTTP_CONTENT_ID.toLowerCase(Locale.ENGLISH));
-
- final String contentType = mimeHeaders.get(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ENGLISH));
- if (contentType == null) {
- throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
- }
- if (isChangeSet) {
- if (HttpContentType.APPLICATION_HTTP.equalsIgnoreCase(contentType)) {
- validateEncoding(mimeHeaders.get(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING.toLowerCase(Locale.ENGLISH)));
- parseNewLine(scanner);// mandatory
- BatchSingleResponseImpl response = parseResponse(scanner, isChangeSet);
- responses.add(response);
- } else {
- throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.APPLICATION_HTTP));
- }
- } else {
- if (HttpContentType.APPLICATION_HTTP.equalsIgnoreCase(contentType)) {
- validateEncoding(mimeHeaders.get(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING.toLowerCase(Locale.ENGLISH)));
- parseNewLine(scanner);// mandatory
- BatchSingleResponseImpl response = parseResponse(scanner, isChangeSet);
- responses.add(response);
- } else if (contentType.matches(REG_EX_OPTIONAL_WHITESPACE + HttpContentType.MULTIPART_MIXED + ANY_CHARACTERS)) {
- String changeSetBoundary = getBoundary(contentType);
- if (boundary.equals(changeSetBoundary)) {
- throw new BatchException(BatchException.INVALID_CHANGESET_BOUNDARY.addContent(currentLineNumber));
- }
- parseNewLine(scanner);// mandatory
- Pattern changeSetCloseDelimiter =
- Pattern.compile("--" + changeSetBoundary + "--" + REG_EX_ZERO_OR_MORE_WHITESPACES);
- while (!scanner.hasNext(changeSetCloseDelimiter)) {
- responses.addAll(parseMultipart(scanner, changeSetBoundary, true));
- }
- scanner.next(changeSetCloseDelimiter);
- currentLineNumber++;
- parseOptionalEmptyLine(scanner);
- } else {
- throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED
- + " or " + HttpContentType.APPLICATION_HTTP));
- }
- }
- } else if (scanner.hasNext(boundary + REG_EX_ZERO_OR_MORE_WHITESPACES)) {
- currentLineNumber++;
- throw new BatchException(BatchException.INVALID_BOUNDARY_DELIMITER.addContent(currentLineNumber));
- } else if (scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
- currentLineNumber++;
- throw new BatchException(BatchException.NO_MATCH_WITH_BOUNDARY_STRING.addContent(boundary).addContent(
- currentLineNumber));
- } else {
- currentLineNumber++;
- throw new BatchException(BatchException.MISSING_BOUNDARY_DELIMITER.addContent(currentLineNumber));
- }
- return responses;
-
- }
-
- private BatchSingleResponseImpl parseResponse(final Scanner scanner, final boolean isChangeSet)
- throws BatchException {
- BatchSingleResponseImpl response = new BatchSingleResponseImpl();
- if (scanner.hasNext(REG_EX_STATUS_LINE)) {
- scanner.next(REG_EX_STATUS_LINE);
- currentLineNumber++;
- final String statusCode;
- final String statusInfo;
- MatchResult result = scanner.match();
- if (result.groupCount() == 2) {
- statusCode = result.group(1);
- statusInfo = result.group(2);
- } else {
- currentLineNumber++;
- throw new BatchException(BatchException.INVALID_STATUS_LINE.addContent(scanner.next()).addContent(
- currentLineNumber));
- }
-
- Map<String, String> headers = parseResponseHeaders(scanner);
- parseNewLine(scanner);
- String body = parseBody(scanner);
- String contentLengthHeader = getHeaderValue(headers, HttpHeaders.CONTENT_LENGTH);
- if (contentLengthHeader != null) {
- int contentLength = Integer.parseInt(contentLengthHeader);
- if (contentLength < body.length()) {
- body = body.substring(0, contentLength);
- }
- }
- response.setStatusCode(statusCode);
- response.setStatusInfo(statusInfo);
- response.setHeaders(headers);
- response.setContentId(currentContentId);
- response.setBody(body);
- } else {
- currentLineNumber++;
- throw new BatchException(BatchException.INVALID_STATUS_LINE.addContent(scanner.next()).addContent(
- currentLineNumber));
- }
- return response;
- }
-
- private void validateEncoding(final String encoding) throws BatchException {
- if (!BatchHelper.BINARY_ENCODING.equalsIgnoreCase(encoding)) {
- throw new BatchException(BatchException.INVALID_CONTENT_TRANSFER_ENCODING);
- }
- }
-
- private Map<String, String> parseMimeHeaders(final Scanner scanner) throws BatchException {
- Map<String, String> headers = new HashMap<String, String>();
- while (scanner.hasNext() && !(scanner.hasNext(REG_EX_BLANK_LINE))) {
- if (scanner.hasNext(REG_EX_HEADER)) {
- scanner.next(REG_EX_HEADER);
- currentLineNumber++;
- MatchResult result = scanner.match();
- if (result.groupCount() == 2) {
- String headerName = result.group(1).trim().toLowerCase(Locale.ENGLISH);
- String headerValue = result.group(2).trim();
- headers.put(headerName, headerValue);
- }
- } else {
- throw new BatchException(BatchException.INVALID_HEADER.addContent(scanner.next()));
- }
- }
- return headers;
- }
-
- private Map<String, String> parseResponseHeaders(final Scanner scanner) throws BatchException {
- Map<String, String> headers = new HashMap<String, String>();
- while (scanner.hasNext() && !scanner.hasNext(REG_EX_BLANK_LINE)) {
- if (scanner.hasNext(REG_EX_HEADER)) {
- scanner.next(REG_EX_HEADER);
- currentLineNumber++;
- MatchResult result = scanner.match();
- if (result.groupCount() == 2) {
- String headerName = result.group(1).trim();
- String headerValue = result.group(2).trim();
- if (BatchHelper.HTTP_CONTENT_ID.equalsIgnoreCase(headerName)) {
- if (currentContentId == null) {
- currentContentId = headerValue;
- }
- } else {
- headers.put(headerName, headerValue);
- }
- }
- } else {
- currentLineNumber++;
- throw new BatchException(BatchException.INVALID_HEADER.addContent(scanner.next())
- .addContent(currentLineNumber));
- }
- }
- return headers;
- }
-
- private String getHeaderValue(final Map<String, String> headers, final String headerName) {
- for (Map.Entry<String, String> header : headers.entrySet()) {
- if (headerName.equalsIgnoreCase(header.getKey())) {
- return header.getValue();
- }
- }
- return null;
- }
-
- private String parseBody(final Scanner scanner) {
- StringBuilder body = null;
- while (scanner.hasNext() && !scanner.hasNext(REG_EX_ANY_BOUNDARY_STRING)) {
- if (body == null) {
- body = new StringBuilder(scanner.next());
- } else {
- body.append(CRLF).append(scanner.next());
- }
- currentLineNumber++;
- }
- String responseBody = body != null ? body.toString() : null;
- return responseBody;
- }
-
- private String getBoundary(final String contentType) throws BatchException {
- Scanner contentTypeScanner = new Scanner(contentType);
- contentTypeScanner.useDelimiter(";\\s?");
- if (contentTypeScanner.hasNext(REG_EX_CONTENT_TYPE)) {
- contentTypeScanner.next(REG_EX_CONTENT_TYPE);
- } else {
- contentTypeScanner.close();
- throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED));
- }
- if (contentTypeScanner.hasNext(REG_EX_BOUNDARY_PARAMETER)) {
- contentTypeScanner.next(REG_EX_BOUNDARY_PARAMETER);
- MatchResult result = contentTypeScanner.match();
- contentTypeScanner.close();
- if (result.groupCount() == 1 && result.group(1).trim().matches(REG_EX_BOUNDARY)) {
- return trimQuota(result.group(1).trim());
- } else {
- throw new BatchException(BatchException.INVALID_BOUNDARY);
- }
- } else {
- contentTypeScanner.close();
- throw new BatchException(BatchException.MISSING_PARAMETER_IN_CONTENT_TYPE);
- }
- }
-
- private void parseNewLine(final Scanner scanner) throws BatchException {
- if (scanner.hasNext() && scanner.hasNext(REG_EX_BLANK_LINE)) {
- scanner.next();
- currentLineNumber++;
- } else {
- currentLineNumber++;
- if (scanner.hasNext()) {
- throw new BatchException(BatchException.MISSING_BLANK_LINE.addContent(scanner.next()).addContent(
- currentLineNumber));
- } else {
- throw new BatchException(BatchException.TRUNCATED_BODY.addContent(currentLineNumber));
-
- }
- }
- }
-
- private void parseOptionalEmptyLine(final Scanner scanner) {
- if (scanner.hasNext() && scanner.hasNext(REG_EX_BLANK_LINE)) {
- scanner.next();
- currentLineNumber++;
- }
- }
-
- private String trimQuota(String boundary) {
- if (boundary.matches("\".*\"")) {
- boundary = boundary.replace("\"", "");
- }
- boundary = boundary.replaceAll("\\)", "\\\\)");
- boundary = boundary.replaceAll("\\(", "\\\\(");
- boundary = boundary.replaceAll("\\?", "\\\\?");
- boundary = boundary.replaceAll("\\+", "\\\\+");
- return boundary;
- }
-
-}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchBodyPart.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchBodyPart.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchBodyPart.java
new file mode 100644
index 0000000..d60c29a
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchBodyPart.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.commons.HttpHeaders;
+import org.apache.olingo.odata2.core.batch.v2.BufferedReaderIncludingLineEndings.Line;
+import org.apache.olingo.odata2.core.batch.v2.Header.HeaderField;
+
+public class BatchBodyPart implements BatchPart {
+ final private String boundary;
+ final private boolean isStrict;
+ final List<Line> remainingMessage = new LinkedList<Line>();
+
+ private Header headers;
+ private boolean isChangeSet;
+ private List<BatchQueryOperation> requests;
+
+ public BatchBodyPart(final List<Line> bodyPartMessage, final String boundary, final boolean isStrict)
+ throws BatchException {
+ this.boundary = boundary;
+ this.isStrict = isStrict;
+
+ remainingMessage.addAll(bodyPartMessage);
+ }
+
+ public BatchBodyPart parse() throws BatchException {
+ headers = BatchParserCommon.consumeHeaders(remainingMessage);
+ BatchParserCommon.consumeBlankLine(remainingMessage, isStrict);
+ isChangeSet = isChangeSet(headers);
+ requests = consumeRequest(remainingMessage);
+
+ return this;
+ }
+
+ private boolean isChangeSet(final Header headers) throws BatchException {
+ final List<String> contentTypes = headers.getHeaders(HttpHeaders.CONTENT_TYPE);
+ boolean isChangeSet = false;
+
+ if (contentTypes.size() == 0) {
+ throw new BatchException(BatchException.MISSING_CONTENT_TYPE.addContent(headers.getLineNumber()));
+ }
+
+ for (String contentType : contentTypes) {
+ if (isContentTypeMultiPartMixed(contentType)) {
+ isChangeSet = true;
+ }
+ }
+
+ return isChangeSet;
+ }
+
+ private boolean isContentTypeMultiPartMixed(final String contentType) {
+ return BatchParserCommon.PATTERN_MULTIPART_BOUNDARY.matcher(contentType).matches();
+ }
+
+ private List<BatchQueryOperation> consumeRequest(final List<Line> remainingMessage) throws BatchException {
+ if (isChangeSet) {
+ return consumeChangeSet(remainingMessage);
+ } else {
+ return consumeQueryOperation(remainingMessage);
+ }
+ }
+
+ private List<BatchQueryOperation> consumeChangeSet(final List<Line> remainingMessage)
+ throws BatchException {
+ final List<List<Line>> changeRequests = splitChangeSet(remainingMessage);
+ final List<BatchQueryOperation> requestList = new LinkedList<BatchQueryOperation>();
+
+ for (List<Line> changeRequest : changeRequests) {
+ requestList.add(new BatchChangeSetPart(changeRequest, isStrict).parse());
+ }
+
+ return requestList;
+ }
+
+ private List<List<Line>> splitChangeSet(final List<Line> remainingMessage)
+ throws BatchException {
+
+ final HeaderField contentTypeField = headers.getHeaderField(HttpHeaders.CONTENT_TYPE);
+ final String changeSetBoundary =
+ BatchParserCommon.getBoundary(contentTypeField.getValueNotNull(), contentTypeField.getLineNumber());
+ validateChangeSetBoundary(changeSetBoundary, headers);
+
+ return BatchParserCommon.splitMessageByBoundary(remainingMessage, changeSetBoundary);
+ }
+
+ private List<BatchQueryOperation> consumeQueryOperation(final List<Line> remainingMessage)
+ throws BatchException {
+ final List<BatchQueryOperation> requestList = new LinkedList<BatchQueryOperation>();
+ requestList.add(new BatchQueryOperation(remainingMessage, isStrict).parse());
+
+ return requestList;
+ }
+
+ private void validateChangeSetBoundary(final String changeSetBoundary, final Header header) throws BatchException {
+ if (changeSetBoundary.equals(boundary)) {
+ throw new BatchException(BatchException.INVALID_BOUNDARY.addContent(header.getHeaderField(
+ HttpHeaders.CONTENT_TYPE).getLineNumber()));
+ }
+ }
+
+ @Override
+ public Header getHeaders() {
+ return headers;
+ }
+
+ @Override
+ public boolean isStrict() {
+ return isStrict;
+ }
+
+ public boolean isChangeSet() {
+ return isChangeSet;
+ }
+
+ public List<BatchQueryOperation> getRequests() {
+ return requests;
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchChangeSetPart.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchChangeSetPart.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchChangeSetPart.java
new file mode 100644
index 0000000..f3b0699
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchChangeSetPart.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+import java.util.List;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.core.batch.v2.BufferedReaderIncludingLineEndings.Line;
+
+public class BatchChangeSetPart extends BatchQueryOperation {
+ private BatchQueryOperation request;
+
+ public BatchChangeSetPart(final List<Line> message, final boolean isStrict) throws BatchException {
+ super(message, isStrict);
+ }
+
+ @Override
+ public BatchChangeSetPart parse() throws BatchException {
+ headers = BatchParserCommon.consumeHeaders(message);
+ BatchParserCommon.consumeBlankLine(message, isStrict);
+
+ request = new BatchQueryOperation(message, isStrict).parse();
+
+ return this;
+ }
+
+ public BatchQueryOperation getRequest() {
+ return request;
+ }
+
+ @Override
+ public List<Line> getBody() {
+ return request.getBody();
+ }
+
+ @Override
+ public Line getHttpStatusLine() {
+ return request.getHttpStatusLine();
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParser.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParser.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParser.java
new file mode 100644
index 0000000..09bf987
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParser.java
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.batch.BatchParserResult;
+import org.apache.olingo.odata2.api.batch.BatchRequestPart;
+import org.apache.olingo.odata2.api.client.batch.BatchSingleResponse;
+import org.apache.olingo.odata2.api.ep.EntityProviderBatchProperties;
+import org.apache.olingo.odata2.api.uri.PathInfo;
+import org.apache.olingo.odata2.api.uri.PathSegment;
+import org.apache.olingo.odata2.core.batch.v2.BufferedReaderIncludingLineEndings.Line;
+import org.apache.olingo.odata2.core.exception.ODataRuntimeException;
+
+public class BatchParser {
+
+ private final PathInfo batchRequestPathInfo;
+ private final String contentTypeMime;
+ private final boolean isStrict;
+
+ public BatchParser(final String contentType, final boolean isStrict) {
+ this(contentType, null, isStrict);
+ }
+
+ public BatchParser(final String contentType, final EntityProviderBatchProperties properties, final boolean isStrict) {
+ contentTypeMime = contentType;
+ batchRequestPathInfo = (properties != null) ? properties.getPathInfo() : null;
+ this.isStrict = isStrict;
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<BatchSingleResponse> parseBatchResponse(final InputStream in) throws BatchException {
+ return (List<BatchSingleResponse>) parse(in, new BatchResponseTransformator());
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<BatchRequestPart> parseBatchRequest(final InputStream in) throws BatchException {
+ return (List<BatchRequestPart>) parse(in, new BatchRequestTransformator());
+ }
+
+ private List<? extends BatchParserResult> parse(final InputStream in, final BatchTransformator transformator)
+ throws BatchException {
+ try {
+ return parseBatch(in, transformator);
+ } catch (IOException e) {
+ throw new ODataRuntimeException(e);
+ } finally {
+ try {
+ in.close();
+ } catch (IOException e) {
+ throw new ODataRuntimeException(e);
+ }
+ }
+ }
+
+ private List<BatchParserResult> parseBatch(final InputStream in,
+ final BatchTransformator transformator) throws BatchException, IOException {
+
+ final String baseUri = getBaseUri();
+ final String boundary = BatchParserCommon.getBoundary(contentTypeMime, 1);
+ final List<BatchParserResult> resultList = new LinkedList<BatchParserResult>();
+ final List<List<Line>> bodyPartStrings = splitBodyParts(in, boundary);
+
+ for (List<Line> bodyPartString : bodyPartStrings) {
+ BatchBodyPart bodyPart = new BatchBodyPart(bodyPartString, boundary, isStrict).parse();
+ resultList.addAll(transformator.transform(bodyPart, batchRequestPathInfo, baseUri));
+ }
+
+ return resultList;
+ }
+
+ private List<List<Line>> splitBodyParts(final InputStream in, final String boundary)
+ throws IOException, BatchException {
+
+ final BufferedReaderIncludingLineEndings reader = new BufferedReaderIncludingLineEndings(new InputStreamReader(in));
+ final List<Line> message = reader.toList();
+ reader.close();
+
+ return BatchParserCommon.splitMessageByBoundary(message, boundary);
+ }
+
+
+ private String getBaseUri() throws BatchException {
+ String baseUri = "";
+
+ if (batchRequestPathInfo != null && batchRequestPathInfo.getServiceRoot() != null) {
+ final String uri = batchRequestPathInfo.getServiceRoot().toASCIIString();
+
+ baseUri = addPathSegements(removeLastSlash(uri));
+ }
+
+ return baseUri;
+ }
+
+ private String addPathSegements(String baseUri) {
+ for (PathSegment precedingPS : batchRequestPathInfo.getPrecedingSegments()) {
+ baseUri = baseUri + "/" + precedingPS.getPath();
+ }
+
+ return baseUri;
+ }
+
+ private String removeLastSlash(String baseUri) {
+ if (baseUri.lastIndexOf('/') == baseUri.length() - 1) {
+ baseUri = baseUri.substring(0, baseUri.length() - 1);
+ }
+
+ return baseUri;
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParserCommon.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParserCommon.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParserCommon.java
new file mode 100644
index 0000000..c2751ee
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchParserCommon.java
@@ -0,0 +1,259 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.commons.HttpContentType;
+import org.apache.olingo.odata2.api.commons.HttpHeaders;
+import org.apache.olingo.odata2.core.batch.AcceptParser;
+import org.apache.olingo.odata2.core.batch.v2.BufferedReaderIncludingLineEndings.Line;
+import org.apache.olingo.odata2.core.commons.Decoder;
+
+public class BatchParserCommon {
+
+ private static final Pattern PATTERN_LAST_CRLF = Pattern.compile("(.*)(\r\n){1}( *)", Pattern.DOTALL);
+
+ private static final String REG_EX_BOUNDARY =
+ "([a-zA-Z0-9_\\-\\.'\\+]{1,70})|\"([a-zA-Z0-9_\\-\\.'\\+\\s\\" +
+ "(\\),/:=\\?]{1,69}[a-zA-Z0-9_\\-\\.'\\+\\(\\),/:=\\?])\""; // See RFC 2046
+
+ private static final String REX_EX_MULTIPART_BOUNDARY = "multipart/mixed;\\s*boundary=(.+)";
+ private static final String REG_EX_APPLICATION_HTTP = "application/http";
+ public static final Pattern PATTERN_MULTIPART_BOUNDARY = Pattern.compile(REX_EX_MULTIPART_BOUNDARY,
+ Pattern.CASE_INSENSITIVE);
+ public static final Pattern PATTERN_HEADER_LINE = Pattern.compile("([a-zA-Z\\-]+):\\s?(.*)\\s*");
+ public static final Pattern PATTERN_CONTENT_TYPE_APPLICATION_HTTP = Pattern.compile(REG_EX_APPLICATION_HTTP,
+ Pattern.CASE_INSENSITIVE);
+ public static final Pattern PATTERN_RELATIVE_URI = Pattern.compile("([^/][^?]*)(\\?.*)?");
+
+ public static String trimLineListToLength(final List<Line> list, final int length) {
+ final String message = lineListToString(list);
+ final int lastIndex = Math.min(length, message.length());
+
+ return (lastIndex > 0) ? message.substring(0, lastIndex) : "";
+ }
+
+ public static String lineListToString(final List<Line> list) {
+ StringBuilder builder = new StringBuilder();
+
+ for (Line currentLine : list) {
+ builder.append(currentLine.toString());
+ }
+
+ return builder.toString();
+ }
+
+ public static InputStream convertLineListToInputStream(final List<Line> messageList, final int contentLength)
+ throws BatchException {
+ final String message = trimLineListToLength(messageList, contentLength);
+
+ return new ByteArrayInputStream(message.getBytes());
+ }
+
+ public static InputStream convertLineListToInputStream(final List<Line> messageList)
+ throws BatchException {
+ final String message = lineListToString(messageList);
+
+ return new ByteArrayInputStream(message.getBytes());
+ }
+
+ static List<List<Line>> splitMessageByBoundary(final List<Line> message, final String boundary)
+ throws BatchException {
+ final List<List<Line>> messageParts = new LinkedList<List<Line>>();
+ List<Line> currentPart = new ArrayList<Line>();
+ boolean isEndReached = false;
+
+ final String quotedBoundary = Pattern.quote(boundary);
+ final Pattern boundaryDelimiterPattern = Pattern.compile("--" + quotedBoundary + "--[\\s ]*");
+ final Pattern boundaryPattern = Pattern.compile("--" + quotedBoundary + "[\\s ]*");
+
+ for (Line currentLine : message) {
+ if (boundaryDelimiterPattern.matcher(currentLine.toString()).matches()) {
+ removeEndingCRLFFromList(currentPart);
+ messageParts.add(currentPart);
+ isEndReached = true;
+ } else if (boundaryPattern.matcher(currentLine.toString()).matches()) {
+ removeEndingCRLFFromList(currentPart);
+ messageParts.add(currentPart);
+ currentPart = new LinkedList<Line>();
+ } else {
+ currentPart.add(currentLine);
+ }
+
+ if (isEndReached) {
+ break;
+ }
+ }
+
+ final int lineNumer = (message.size() > 0) ? message.get(0).getLineNumber() : 0;
+ // Remove preamble
+ if (messageParts.size() > 0) {
+ messageParts.remove(0);
+ } else {
+
+ throw new BatchException(BatchException.MISSING_BOUNDARY_DELIMITER.addContent(lineNumer));
+ }
+
+ if (!isEndReached) {
+ throw new BatchException(BatchException.MISSING_CLOSE_DELIMITER.addContent(lineNumer));
+ }
+
+ if (messageParts.size() == 0) {
+ throw new BatchException(BatchException.NO_MATCH_WITH_BOUNDARY_STRING.addContent(boundary).addContent(lineNumer));
+ }
+
+ return messageParts;
+ }
+
+ private static void removeEndingCRLFFromList(final List<Line> list) {
+ if (list.size() > 0) {
+ Line lastLine = list.remove(list.size() - 1);
+ list.add(removeEndingCRLF(lastLine));
+ }
+ }
+
+ public static Line removeEndingCRLF(final Line line) {
+ Pattern pattern = PATTERN_LAST_CRLF;
+ Matcher matcher = pattern.matcher(line.toString());
+
+ if (matcher.matches()) {
+ return new Line(matcher.group(1), line.getLineNumber());
+ } else {
+ return line;
+ }
+ }
+
+ public static Header consumeHeaders(final List<Line> remainingMessage) throws BatchException {
+ final int headerLineNumber = remainingMessage.size() != 0 ? remainingMessage.get(0).getLineNumber() : 0;
+ final Header headers = new Header(headerLineNumber);
+ final Iterator<Line> iter = remainingMessage.iterator();
+ final AcceptParser acceptParser = new AcceptParser();
+ Line currentLine;
+ int acceptLineNumber = 0;
+ int acceptLanguageLineNumber = 0;
+ boolean isHeader = true;
+
+ while (iter.hasNext() && isHeader) {
+ currentLine = iter.next();
+ final Matcher headerMatcher = PATTERN_HEADER_LINE.matcher(currentLine.toString());
+
+ if (headerMatcher.matches() && headerMatcher.groupCount() == 2) {
+ iter.remove();
+
+ String headerName = headerMatcher.group(1).trim();
+ String headerValue = headerMatcher.group(2).trim();
+
+ if (HttpHeaders.ACCEPT.equalsIgnoreCase(headerName)) {
+ acceptParser.addAcceptHeaderValue(headerValue);
+ acceptLineNumber = currentLine.getLineNumber();
+ } else if (HttpHeaders.ACCEPT_LANGUAGE.equalsIgnoreCase(headerName)) {
+ acceptParser.addAcceptLanguageHeaderValue(headerValue);
+ acceptLanguageLineNumber = currentLine.getLineNumber();
+ } else {
+ headers.addHeader(headerName, Header.splitValuesByComma(headerValue), currentLine.getLineNumber());
+ }
+ } else {
+ isHeader = false;
+ }
+ }
+
+ headers.addHeader(HttpHeaders.ACCEPT, acceptParser.parseAcceptHeaders(), acceptLineNumber);
+ headers.addHeader(HttpHeaders.ACCEPT_LANGUAGE, acceptParser.parseAcceptableLanguages(), acceptLanguageLineNumber);
+
+ return headers;
+ }
+
+ public static void consumeBlankLine(final List<Line> remainingMessage, final boolean isStrict)
+ throws BatchException {
+ if (remainingMessage.size() > 0 && remainingMessage.get(0).toString().matches("\\s*\r\n\\s*")) {
+ remainingMessage.remove(0);
+ } else {
+ if (isStrict) {
+ final int lineNumber = (remainingMessage.size() > 0) ? remainingMessage.get(0).getLineNumber() : 0;
+ throw new BatchException(BatchException.MISSING_BLANK_LINE.addContent("[None]").addContent(lineNumber));
+ }
+ }
+ }
+
+ public static String getBoundary(final String contentType, final int line) throws BatchException {
+ final Matcher boundaryMatcher = PATTERN_MULTIPART_BOUNDARY.matcher(contentType);
+
+ if (boundaryMatcher.matches()) {
+ final String boundary = boundaryMatcher.group(1);
+ if (boundary.matches(REG_EX_BOUNDARY)) {
+ return trimQuota(boundary);
+ } else {
+ throw new BatchException(BatchException.INVALID_BOUNDARY.addContent(line));
+ }
+ } else {
+ throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(HttpContentType.MULTIPART_MIXED));
+ }
+ }
+
+ private static String trimQuota(String boundary) {
+ if (boundary.matches("\".*\"")) {
+ boundary = boundary.replace("\"", "");
+ }
+
+ return boundary;
+ }
+
+ public static Map<String, List<String>> parseQueryParameter(final Line httpRequest) {
+ Map<String, List<String>> queryParameter = new HashMap<String, List<String>>();
+
+ String[] requestParts = httpRequest.toString().split(" ");
+ if (requestParts.length == 3) {
+
+ String[] parts = requestParts[1].split("\\?");
+ if (parts.length == 2) {
+ String[] parameters = parts[1].split("&");
+
+ for (String parameter : parameters) {
+ String[] parameterParts = parameter.split("=");
+ String parameterName = parameterParts[0].toLowerCase(Locale.ENGLISH);
+
+ if (parameterParts.length == 2) {
+ List<String> valueList = queryParameter.get(parameterName);
+ valueList = valueList == null ? new LinkedList<String>() : valueList;
+ queryParameter.put(parameterName, valueList);
+
+ String[] valueParts = parameterParts[1].split(",");
+ for (String value : valueParts) {
+ valueList.add(Decoder.decode(value));
+ }
+ }
+ }
+ }
+ }
+
+ return queryParameter;
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchPart.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchPart.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchPart.java
new file mode 100644
index 0000000..69f211f
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchPart.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+public interface BatchPart {
+ public Header getHeaders();
+
+ public boolean isStrict();
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchQueryOperation.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchQueryOperation.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchQueryOperation.java
new file mode 100644
index 0000000..9bbd019
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchQueryOperation.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+import java.util.List;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.core.batch.v2.BufferedReaderIncludingLineEndings.Line;
+
+public class BatchQueryOperation implements BatchPart {
+
+ protected final boolean isStrict;
+ protected Line httpStatusLine;
+ protected Header headers;
+ protected List<Line> body;
+ protected int bodySize;
+ protected List<Line> message;
+
+ public BatchQueryOperation(final List<Line> message, final boolean isStrict) {
+ this.isStrict = isStrict;
+ this.message = message;
+ }
+
+ public BatchQueryOperation parse() throws BatchException {
+ httpStatusLine = consumeHttpStatusLine(message);
+ headers = BatchParserCommon.consumeHeaders(message);
+ BatchParserCommon.consumeBlankLine(message, isStrict);
+ body = message;
+
+ return this;
+ }
+
+ protected Line consumeHttpStatusLine(final List<Line> message) throws BatchException {
+ if (message.size() > 0 && !message.get(0).toString().trim().equals("")) {
+ final Line method = message.get(0);
+ message.remove(0);
+
+ return method;
+ } else {
+ final int line = (message.size() > 0) ? message.get(0).getLineNumber() : 0;
+ throw new BatchException(BatchException.MISSING_METHOD.addContent(line));
+ }
+ }
+
+ public Line getHttpStatusLine() {
+ return httpStatusLine;
+ }
+
+ public List<Line> getBody() {
+ return body;
+ }
+
+ public int getBodySize() {
+ return bodySize;
+ }
+
+ @Override
+ public Header getHeaders() {
+ return headers;
+ }
+
+ @Override
+ public boolean isStrict() {
+ return isStrict;
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchRequestTransformator.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchRequestTransformator.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchRequestTransformator.java
new file mode 100644
index 0000000..66600b5
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchRequestTransformator.java
@@ -0,0 +1,175 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.batch.BatchParserResult;
+import org.apache.olingo.odata2.api.commons.HttpHeaders;
+import org.apache.olingo.odata2.api.commons.ODataHttpMethod;
+import org.apache.olingo.odata2.api.processor.ODataRequest;
+import org.apache.olingo.odata2.api.processor.ODataRequest.ODataRequestBuilder;
+import org.apache.olingo.odata2.api.uri.PathInfo;
+import org.apache.olingo.odata2.core.batch.BatchHelper;
+import org.apache.olingo.odata2.core.batch.BatchRequestPartImpl;
+import org.apache.olingo.odata2.core.batch.v2.BatchTransformatorCommon.HttpRequestStatusLine;
+import org.apache.olingo.odata2.core.batch.v2.Header.HeaderField;
+
+public class BatchRequestTransformator implements BatchTransformator {
+
+ @Override
+ public List<BatchParserResult> transform(final BatchBodyPart bodyPart, final PathInfo pathInfo, final String baseUri)
+ throws BatchException {
+
+ final List<ODataRequest> requests = new LinkedList<ODataRequest>();
+ final List<BatchParserResult> resultList = new ArrayList<BatchParserResult>();
+
+ validateHeader(bodyPart, false);
+
+ for (BatchQueryOperation queryOperation : bodyPart.getRequests()) {
+ requests.add(processQueryOperation(bodyPart, pathInfo, baseUri, queryOperation));
+ }
+
+ resultList.add(new BatchRequestPartImpl(bodyPart.isChangeSet(), requests));
+ return resultList;
+ }
+
+ private void validateHeader(final BatchPart bodyPart, final boolean isChangeSet) throws BatchException {
+ Header headers = bodyPart.getHeaders();
+
+ BatchTransformatorCommon.validateContentType(headers);
+ BatchTransformatorCommon.validateContentTransferEncoding(headers, isChangeSet);
+ }
+
+ private ODataRequest processQueryOperation(final BatchBodyPart bodyPart, final PathInfo pathInfo,
+ final String baseUri, final BatchQueryOperation queryOperation) throws BatchException {
+
+ if (bodyPart.isChangeSet()) {
+ BatchQueryOperation encapsulatedQueryOperation = ((BatchChangeSetPart) queryOperation).getRequest();
+ Header headers = transformHeader(encapsulatedQueryOperation, queryOperation);
+ validateHeader(queryOperation, true);
+
+ return createRequest(queryOperation, headers, pathInfo, baseUri, bodyPart.isChangeSet());
+ } else {
+
+ Header headers = transformHeader(queryOperation, bodyPart);
+ return createRequest(queryOperation, headers, pathInfo, baseUri, bodyPart.isChangeSet());
+ }
+ }
+
+ private ODataRequest createRequest(final BatchQueryOperation operation, final Header headers,
+ final PathInfo pathInfo, final String baseUri, final boolean isChangeSet) throws BatchException {
+
+ final HttpRequestStatusLine statusLine = new HttpRequestStatusLine( operation.getHttpStatusLine(),
+ baseUri,
+ pathInfo);
+ statusLine.validateHttpMethod(isChangeSet);
+
+ validateBody(statusLine, operation);
+ InputStream bodyStrean = getBodyStream(operation, headers, statusLine);
+
+ ODataRequestBuilder requestBuilder = ODataRequest.method(statusLine.getMethod())
+ .acceptableLanguages(getAcceptLanguageHeaders(headers))
+ .acceptHeaders(headers.getHeaders(HttpHeaders.ACCEPT))
+ .allQueryParameters(BatchParserCommon.parseQueryParameter(operation.getHttpStatusLine()))
+ .body(bodyStrean)
+ .requestHeaders(headers.toMultiMap())
+ .pathInfo(statusLine.getPathInfo());
+
+ final String contentType = headers.getHeader(HttpHeaders.CONTENT_TYPE);
+ if (contentType != null) {
+ requestBuilder.contentType(contentType);
+ }
+
+ return requestBuilder.build();
+ }
+
+ private void validateBody(final HttpRequestStatusLine httpStatusLine, final BatchQueryOperation operation)
+ throws BatchException {
+ if (httpStatusLine.getMethod().equals(ODataHttpMethod.GET) && isUnvalidGetRequestBody(operation)) {
+ throw new BatchException(BatchException.INVALID_REQUEST_LINE
+ .addContent(httpStatusLine.getMethod())
+ .addContent(httpStatusLine.getLineNumber()));
+ }
+ }
+
+ private boolean isUnvalidGetRequestBody(final BatchQueryOperation operation) {
+ return (operation.getBody().size() > 1)
+ || (operation.getBody().size() == 1 && !"".equals(operation.getBody().get(0).toString().trim()));
+ }
+
+ private InputStream getBodyStream(final BatchQueryOperation operation, final Header headers,
+ final HttpRequestStatusLine httpStatusLine) throws BatchException {
+
+ if (httpStatusLine.getMethod().equals(ODataHttpMethod.GET)) {
+ return new ByteArrayInputStream(new byte[0]);
+ } else {
+ int contentLength = BatchTransformatorCommon.getContentLength(headers);
+
+ if (contentLength == -1) {
+ return BatchParserCommon.convertLineListToInputStream(operation.getBody());
+ } else {
+ return BatchParserCommon.convertLineListToInputStream(operation.getBody(), contentLength);
+ }
+ }
+ }
+
+ private Header transformHeader(final BatchPart operation, final BatchPart parentPart) {
+ final Header headers = operation.getHeaders().clone();
+ headers.removeHeader(BatchHelper.HTTP_CONTENT_ID);
+ final HeaderField operationHeader = operation.getHeaders().getHeaderField(BatchHelper.HTTP_CONTENT_ID);
+ final HeaderField parentHeader = parentPart.getHeaders().getHeaderField(BatchHelper.HTTP_CONTENT_ID);
+
+ if (operationHeader != null && operationHeader.getValues().size() != 0) {
+ headers.addHeader(BatchHelper.REQUEST_HEADER_CONTENT_ID, operationHeader.getValues(), operationHeader
+ .getLineNumber());
+ }
+
+ if (parentHeader != null && parentHeader.getValues().size() != 0) {
+ headers.addHeader(BatchHelper.MIME_HEADER_CONTENT_ID, parentHeader.getValues(), parentHeader.getLineNumber());
+ }
+
+ return headers;
+ }
+
+ private List<Locale> getAcceptLanguageHeaders(final Header headers) {
+ final List<String> acceptLanguageValues = headers.getHeaders(HttpHeaders.ACCEPT_LANGUAGE);
+ List<Locale> acceptLanguages = new ArrayList<Locale>();
+
+ for (String acceptLanguage : acceptLanguageValues) {
+ String[] part = acceptLanguage.split("-");
+ String language = part[0];
+ String country = "";
+ if (part.length == 2) {
+ country = part[part.length - 1];
+ }
+ Locale locale = new Locale(language, country);
+ acceptLanguages.add(locale);
+ }
+
+ return acceptLanguages;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchResponseTransformator.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchResponseTransformator.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchResponseTransformator.java
new file mode 100644
index 0000000..5e4198c
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchResponseTransformator.java
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.batch.BatchParserResult;
+import org.apache.olingo.odata2.api.client.batch.BatchSingleResponse;
+import org.apache.olingo.odata2.api.uri.PathInfo;
+import org.apache.olingo.odata2.core.batch.BatchHelper;
+import org.apache.olingo.odata2.core.batch.BatchSingleResponseImpl;
+import org.apache.olingo.odata2.core.batch.v2.BatchTransformatorCommon.HttpResponsetStatusLine;
+
+public class BatchResponseTransformator implements BatchTransformator {
+
+ public BatchResponseTransformator() {}
+
+ @Override
+ public List<BatchParserResult> transform(final BatchBodyPart bodyPart, final PathInfo pathInfo, final String baseUri)
+ throws BatchException {
+ return processQueryOperation(bodyPart, pathInfo, baseUri);
+ }
+
+ private List<BatchParserResult> processQueryOperation(final BatchBodyPart bodyPart,
+ final PathInfo pathInfo,
+ final String baseUri) throws BatchException {
+
+ List<BatchParserResult> resultList = new ArrayList<BatchParserResult>();
+
+ BatchTransformatorCommon.validateContentType(bodyPart.getHeaders());
+ resultList.addAll(handleBodyPart(bodyPart));
+
+ return resultList;
+ }
+
+ private List<BatchParserResult> handleBodyPart(final BatchBodyPart bodyPart) throws BatchException {
+ List<BatchParserResult> bodyPartResult = new ArrayList<BatchParserResult>();
+
+ if (bodyPart.isChangeSet()) {
+ for (BatchQueryOperation operation : bodyPart.getRequests()) {
+ bodyPartResult.add(transformChangeSet((BatchChangeSetPart) operation));
+ }
+ } else {
+ final String contentId = bodyPart.getHeaders().getHeader(BatchHelper.HTTP_CONTENT_ID);
+
+ bodyPartResult.add(transformQueryOperation(bodyPart.getRequests().get(0), contentId));
+ }
+
+ return bodyPartResult;
+ }
+
+ private BatchSingleResponse transformChangeSet(final BatchChangeSetPart changeSet) throws BatchException {
+ BatchTransformatorCommon.validateContentTransferEncoding(changeSet.getHeaders(), true);
+ final String contentId = changeSet.getHeaders().getHeader(BatchHelper.HTTP_CONTENT_ID);
+
+ return transformQueryOperation(changeSet.getRequest(), contentId);
+ }
+
+ private BatchSingleResponse transformQueryOperation(final BatchQueryOperation operation, final String contentId)
+ throws BatchException {
+
+ final HttpResponsetStatusLine statusLine = new HttpResponsetStatusLine(operation.getHttpStatusLine());
+
+ BatchSingleResponseImpl response = new BatchSingleResponseImpl();
+ response.setContentId(contentId);
+ response.setHeaders(operation.getHeaders().toSingleMap());
+ response.setStatusCode(statusLine.getStatusCode());
+ response.setStatusInfo(statusLine.getStatusInfo());
+ response.setBody(getBody(operation));
+
+ return response;
+ }
+
+ private String getBody(final BatchQueryOperation operation) throws BatchException {
+ int contentLength = BatchTransformatorCommon.getContentLength(operation.getHeaders());
+
+ if (contentLength == -1) {
+ return BatchParserCommon.lineListToString(operation.getBody());
+ } else {
+ return BatchParserCommon.trimLineListToLength(operation.getBody(), contentLength);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchTransformator.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchTransformator.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchTransformator.java
new file mode 100644
index 0000000..5dcddbf
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchTransformator.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+import java.util.List;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.batch.BatchParserResult;
+import org.apache.olingo.odata2.api.uri.PathInfo;
+
+public interface BatchTransformator {
+ public List<BatchParserResult> transform(BatchBodyPart bodyPart, PathInfo pathInfo, String baseUri)
+ throws BatchException;
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchTransformatorCommon.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchTransformatorCommon.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchTransformatorCommon.java
new file mode 100644
index 0000000..c07c06f
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BatchTransformatorCommon.java
@@ -0,0 +1,276 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.olingo.odata2.api.batch.BatchException;
+import org.apache.olingo.odata2.api.commons.HttpContentType;
+import org.apache.olingo.odata2.api.commons.HttpHeaders;
+import org.apache.olingo.odata2.api.commons.ODataHttpMethod;
+import org.apache.olingo.odata2.api.uri.PathInfo;
+import org.apache.olingo.odata2.api.uri.PathSegment;
+import org.apache.olingo.odata2.core.ODataPathSegmentImpl;
+import org.apache.olingo.odata2.core.PathInfoImpl;
+import org.apache.olingo.odata2.core.batch.BatchHelper;
+import org.apache.olingo.odata2.core.batch.v2.BufferedReaderIncludingLineEndings.Line;
+import org.apache.olingo.odata2.core.batch.v2.Header.HeaderField;
+
+public class BatchTransformatorCommon {
+ public static void validateContentType(final Header headers) throws BatchException {
+ List<String> contentTypes = headers.getHeaders(HttpHeaders.CONTENT_TYPE);
+
+ if (contentTypes.size() == 0) {
+ throw new BatchException(BatchException.MISSING_CONTENT_TYPE);
+ }
+ if (!headers.isHeaderMatching(HttpHeaders.CONTENT_TYPE, BatchParserCommon.PATTERN_MULTIPART_BOUNDARY)
+ & !headers.isHeaderMatching(HttpHeaders.CONTENT_TYPE, BatchParserCommon.PATTERN_CONTENT_TYPE_APPLICATION_HTTP)) {
+ throw new BatchException(BatchException.INVALID_CONTENT_TYPE.addContent(
+ HttpContentType.MULTIPART_MIXED + " or " + HttpContentType.APPLICATION_HTTP));
+ }
+ }
+
+ public static void validateContentTransferEncoding(final Header headers, final boolean isChangeRequest)
+ throws BatchException {
+ final HeaderField contentTransferField = headers.getHeaderField(BatchHelper.HTTP_CONTENT_TRANSFER_ENCODING);
+
+ if (contentTransferField != null) {
+ final List<String> contentTransferValues = contentTransferField.getValues();
+ if (contentTransferValues.size() == 1) {
+ String encoding = contentTransferValues.get(0);
+
+ if (!BatchHelper.BINARY_ENCODING.equalsIgnoreCase(encoding)) {
+ throw new BatchException(
+ BatchException.INVALID_CONTENT_TRANSFER_ENCODING.addContent(contentTransferField.getLineNumber()));
+ }
+ } else {
+ throw new BatchException(BatchException.INVALID_HEADER.addContent(contentTransferField.getLineNumber()));
+ }
+ } else {
+ if (isChangeRequest) {
+ throw new BatchException(BatchException.INVALID_CONTENT_TRANSFER_ENCODING.addContent(headers.getLineNumber()));
+ }
+ }
+ }
+
+ public static int getContentLength(final Header headers) throws BatchException {
+ final HeaderField contentLengthField = headers.getHeaderField(HttpHeaders.CONTENT_LENGTH);
+
+ if (contentLengthField != null && contentLengthField.getValues().size() == 1) {
+ final List<String> contentLengthValues = contentLengthField.getValues();
+
+ try {
+ int contentLength = Integer.parseInt(contentLengthValues.get(0));
+
+ if (contentLength < 0) {
+ throw new BatchException(BatchException.INVALID_HEADER.addContent(contentLengthField.getValue()).addContent(
+ contentLengthField.getLineNumber()));
+ }
+
+ return contentLength;
+ } catch (NumberFormatException e) {
+ throw new BatchException(BatchException.INVALID_HEADER.addContent(contentLengthField.getValue()).addContent(
+ contentLengthField.getLineNumber()), e);
+ }
+ }
+
+ return -1;
+ }
+
+ public static class HttpResponsetStatusLine {
+ private static final String REG_EX_STATUS_LINE = "(?:HTTP/[0-9]\\.[0-9])\\s([0-9]{3})\\s([\\S ]+)\\s*";
+ private Line httpStatusLine;
+ private String statusCode;
+ private String statusInfo;
+
+ public HttpResponsetStatusLine(final Line httpStatusLine) throws BatchException {
+ this.httpStatusLine = httpStatusLine;
+ parse();
+ }
+
+ private void parse() throws BatchException {
+ final Pattern regexPattern = Pattern.compile(REG_EX_STATUS_LINE);
+ final Matcher matcher = regexPattern.matcher(httpStatusLine.toString());
+
+ if (matcher.find()) {
+ statusCode = matcher.group(1);
+ statusInfo = matcher.group(2);
+ } else {
+ throw new BatchException(BatchException.INVALID_STATUS_LINE.addContent(httpStatusLine.toString())
+ .addContent(httpStatusLine.getLineNumber()));
+ }
+ }
+
+ public String getStatusCode() {
+ return statusCode;
+ }
+
+ public String getStatusInfo() {
+ return statusInfo;
+ }
+ }
+
+ public static class HttpRequestStatusLine {
+ private static final Set<String> HTTP_BATCH_METHODS = new HashSet<String>(Arrays.asList(new String[] { "GET" }));
+ private static final Set<String> HTTP_CHANGE_SET_METHODS = new HashSet<String>(Arrays.asList(new String[] { "POST",
+ "PUT", "DELETE", "MERGE", "PATCH" }));
+ private static final String HTTP_VERSION = "HTTP/1.1";
+
+ final private Line statusLine;
+ final String requestBaseUri;
+ final PathInfo batchRequestPathInfo;
+
+ private ODataHttpMethod method;
+ private PathInfo pathInfo;
+ private String httpVersion;
+
+ public HttpRequestStatusLine(final Line httpStatusLine, final String baseUri, final PathInfo pathInfo)
+ throws BatchException {
+ statusLine = httpStatusLine;
+ requestBaseUri = baseUri;
+ batchRequestPathInfo = pathInfo;
+
+ parse();
+ }
+
+ private void parse() throws BatchException {
+ final String[] parts = statusLine.toString().split(" ");
+
+ if (parts.length == 3) {
+ try {
+ method = parseMethod(parts[0]);
+ pathInfo = parseUri(parts[1]);
+ httpVersion = parseHttpVersion(parts[2]);
+ } catch (IllegalArgumentException e) {
+ throw new BatchException(BatchException.MISSING_METHOD.addContent(statusLine.getLineNumber()), e);
+ }
+ } else {
+ throw new BatchException(BatchException.INVALID_REQUEST_LINE.addContent(statusLine.toString())
+ .addContent(statusLine.getLineNumber()));
+ }
+ }
+
+ private ODataHttpMethod parseMethod(final String method) throws BatchException {
+ try {
+ return ODataHttpMethod.valueOf(method.trim());
+ } catch (IllegalArgumentException e) {
+ throw new BatchException(BatchException.MISSING_METHOD.addContent(statusLine.getLineNumber()), e);
+ }
+ }
+
+ private PathInfo parseUri(final String uri)
+ throws BatchException {
+ final PathInfoImpl pathInfo = new PathInfoImpl();
+ final String odataPathSegmentsAsString;
+ final String queryParametersAsString;
+ Pattern regexRequestUri;
+
+ pathInfo.setServiceRoot(batchRequestPathInfo.getServiceRoot());
+ pathInfo.setPrecedingPathSegment(batchRequestPathInfo.getPrecedingSegments());
+
+ try {
+ URI uriObject = new URI(uri);
+ if (uriObject.isAbsolute()) {
+ regexRequestUri = Pattern.compile(requestBaseUri + "/([^/][^?]*)(\\?.*)?");
+ } else {
+ regexRequestUri = BatchParserCommon.PATTERN_RELATIVE_URI;
+
+ }
+
+ Matcher uriParts = regexRequestUri.matcher(uri);
+
+ if (uriParts.lookingAt() && uriParts.groupCount() == 2) {
+ odataPathSegmentsAsString = uriParts.group(1);
+ queryParametersAsString = uriParts.group(2) != null ? uriParts.group(2) : "";
+
+ pathInfo.setODataPathSegment(parseODataPathSegments(odataPathSegmentsAsString));
+ if (!odataPathSegmentsAsString.startsWith("$")) {
+ String requestUri = requestBaseUri + "/" + odataPathSegmentsAsString + queryParametersAsString;
+ pathInfo.setRequestUri(new URI(requestUri));
+ }
+
+ } else {
+ throw new BatchException(BatchException.INVALID_URI.addContent(statusLine.getLineNumber()));
+ }
+ } catch (URISyntaxException e) {
+ throw new BatchException(BatchException.INVALID_URI.addContent(statusLine.getLineNumber()), e);
+ }
+
+ return pathInfo;
+ }
+
+ private List<PathSegment> parseODataPathSegments(final String odataPathSegmentsAsString) {
+ final List<PathSegment> odataPathSegments = new ArrayList<PathSegment>();
+ final String[] pathParts = odataPathSegmentsAsString.split("/");
+
+ for (final String pathSegment : pathParts) {
+ odataPathSegments.add(new ODataPathSegmentImpl(pathSegment, null));
+ }
+
+ return odataPathSegments;
+ }
+
+ private String parseHttpVersion(final String httpVersion) throws BatchException {
+ if (!HTTP_VERSION.equals(httpVersion.trim())) {
+ throw new BatchException(BatchException.INVALID_REQUEST_LINE
+ .addContent(statusLine.toString())
+ .addContent(statusLine.getLineNumber()));
+ } else {
+ return HTTP_VERSION;
+ }
+ }
+
+ public void validateHttpMethod(boolean isChangeSet) throws BatchException {
+ Set<String> validMethods = (isChangeSet) ? HTTP_CHANGE_SET_METHODS : HTTP_BATCH_METHODS;
+
+ if (!validMethods.contains(getMethod().toString())) {
+ if (isChangeSet) {
+ throw new BatchException(BatchException.INVALID_CHANGESET_METHOD.addContent(statusLine.getLineNumber()));
+ } else {
+ throw new BatchException(BatchException.INVALID_QUERY_OPERATION_METHOD
+ .addContent(statusLine.getLineNumber()));
+ }
+ }
+ }
+
+ public ODataHttpMethod getMethod() {
+ return method;
+ }
+
+ public PathInfo getPathInfo() {
+ return pathInfo;
+ }
+
+ public String getHttpVersion() {
+ return httpVersion;
+ }
+
+ public int getLineNumber() {
+ return statusLine.getLineNumber();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BufferedReaderIncludingLineEndings.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BufferedReaderIncludingLineEndings.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BufferedReaderIncludingLineEndings.java
new file mode 100644
index 0000000..5c7fb7c
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/BufferedReaderIncludingLineEndings.java
@@ -0,0 +1,275 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BufferedReaderIncludingLineEndings extends Reader {
+ private static final char CR = '\r';
+ private static final char LF = '\n';
+ private static final int EOF = -1;
+ private static final int BUFFER_SIZE = 8192;
+ private Reader reader;
+ private char[] buffer;
+ private int offset = 0;
+ private int limit = 0;
+
+ public BufferedReaderIncludingLineEndings(final Reader reader) {
+ this(reader, BUFFER_SIZE);
+ }
+
+ public BufferedReaderIncludingLineEndings(final Reader reader, final int bufferSize) {
+ if (bufferSize <= 0) {
+ throw new IllegalArgumentException("Buffer size must be greater than zero.");
+ }
+
+ this.reader = reader;
+ buffer = new char[bufferSize];
+ }
+
+ @Override
+ public int read(final char[] charBuffer, final int bufferOffset, final int length) throws IOException {
+ if ((bufferOffset + length) > charBuffer.length) {
+ throw new IndexOutOfBoundsException("Buffer is too small");
+ }
+
+ if (length < 0 || bufferOffset < 0) {
+ throw new IndexOutOfBoundsException("Offset and length must be grater than zero");
+ }
+
+ // Check if buffer is filled. Return if EOF is reached
+ // Is buffer refill required
+ if (limit == offset || isEOF()) {
+ fillBuffer();
+
+ if (isEOF()) {
+ return EOF;
+ }
+ }
+
+ int bytesRead = 0;
+ int bytesToRead = length;
+ int currentOutputOffset = bufferOffset;
+
+ while (bytesToRead != 0) {
+ // Is buffer refill required?
+ if (limit == offset) {
+ fillBuffer();
+
+ if (isEOF()) {
+ bytesToRead = 0;
+ }
+ }
+
+ if (bytesToRead > 0) {
+ int readByte = Math.min(limit - offset, bytesToRead);
+ bytesRead += readByte;
+ bytesToRead -= readByte;
+
+ for (int i = 0; i < readByte; i++) {
+ charBuffer[currentOutputOffset++] = buffer[offset++];
+ }
+ }
+ }
+
+ return bytesRead;
+ }
+
+ public List<Line> toList() throws IOException {
+ final List<Line> result = new ArrayList<Line>();
+ String currentLine;
+ int counter = 1;
+
+ while ((currentLine = readLine()) != null) {
+ result.add(new Line(currentLine, counter++));
+ }
+
+ return result;
+ }
+
+ public String readLine() throws IOException {
+ if (limit == EOF) {
+ return null;
+ }
+
+ final StringBuilder stringBuffer = new StringBuilder();
+ boolean foundLineEnd = false; // EOF will be considered as line ending
+
+ while (!foundLineEnd) {
+ // Is buffer refill required?
+ if (limit == offset) {
+ if (fillBuffer() == EOF) {
+ foundLineEnd = true;
+ }
+ }
+
+ if (!foundLineEnd) {
+ char currentChar = buffer[offset++];
+ stringBuffer.append(currentChar);
+
+ if (currentChar == LF) {
+ foundLineEnd = true;
+ } else if (currentChar == CR) {
+ foundLineEnd = true;
+
+ // Check next char. Consume \n if available
+ // Is buffer refill required?
+ if (limit == offset) {
+ fillBuffer();
+ }
+
+ // Check if there is at least one character
+ if (limit != EOF && buffer[offset] == LF) {
+ stringBuffer.append(LF);
+ offset++;
+ }
+ }
+ }
+ }
+
+ return (stringBuffer.length() == 0) ? null : stringBuffer.toString();
+ }
+
+ @Override
+ public void close() throws IOException {
+ reader.close();
+ }
+
+ @Override
+ public boolean ready() throws IOException {
+ // Not EOF and buffer refill is not required
+ return !isEOF() && !(limit == offset);
+ }
+
+ @Override
+ public void reset() throws IOException {
+ throw new IOException("Reset is not supported");
+ }
+
+ @Override
+ public void mark(final int readAheadLimit) throws IOException {
+ throw new IOException("Mark is not supported");
+ }
+
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+ @Override
+ public long skip(final long n) throws IOException {
+ if (n == 0) {
+ return 0;
+ } else if (n < 0) {
+ throw new IllegalArgumentException("skip value is negative");
+ } else {
+ long charactersToSkip = n;
+ long charactersSkiped = 0;
+
+ while (charactersToSkip != 0) {
+ // Is buffer refill required?
+ if (limit == offset) {
+ fillBuffer();
+
+ if (isEOF()) {
+ charactersToSkip = 0;
+ }
+ }
+
+ // Check if more characters are available
+ if (!isEOF()) {
+ int skipChars = (int) Math.min(limit - offset, charactersToSkip);
+
+ charactersSkiped += skipChars;
+ charactersToSkip -= skipChars;
+ offset += skipChars;
+ }
+ }
+
+ return charactersSkiped;
+ }
+ }
+
+ private boolean isEOF() {
+ return limit == EOF;
+ }
+
+ private int fillBuffer() throws IOException {
+ limit = reader.read(buffer, 0, buffer.length);
+ offset = 0;
+
+ return limit;
+ }
+
+ public static class Line {
+ private final int lineNumber;
+ private final String content;
+
+ public Line(final String content, final int lineNumber) {
+ this.content = content;
+ this.lineNumber = lineNumber;
+ }
+
+ public int getLineNumber() {
+ return lineNumber;
+ }
+
+ @Override
+ public String toString() {
+ return content;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((content == null) ? 0 : content.hashCode());
+ result = prime * result + lineNumber;
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Line other = (Line) obj;
+ if (content == null) {
+ if (other.content != null) {
+ return false;
+ }
+ } else if (!content.equals(other.content)) {
+ return false;
+ }
+ if (lineNumber != other.lineNumber) {
+ return false;
+ }
+ return true;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/a6e2fbe5/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/Header.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/Header.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/Header.java
new file mode 100644
index 0000000..ba44b25
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/batch/v2/Header.java
@@ -0,0 +1,248 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.olingo.odata2.core.batch.v2;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+public class Header implements Cloneable {
+
+ private final Map<String, HeaderField> headers = new HashMap<String, HeaderField>();
+ private int lineNumber;
+
+ public Header(final int lineNumer) {
+ lineNumber = lineNumer;
+ }
+
+ public void addHeader(final String name, final String value, final int lineNumber) {
+ final HeaderField headerField = getHeaderFieldOrDefault(name, lineNumber);
+ final List<String> headerValues = headerField.getValues();
+
+ if (!headerValues.contains(value)) {
+ headerValues.add(value);
+ }
+ }
+
+ public void addHeader(final String name, final List<String> values, final int lineNumber) {
+ final HeaderField headerField = getHeaderFieldOrDefault(name, lineNumber);
+ final List<String> headerValues = headerField.getValues();
+
+ for (final String value : values) {
+ if (!headerValues.contains(value)) {
+ headerValues.add(value);
+ }
+ }
+ }
+
+ public boolean isHeaderMatching(final String name, final Pattern pattern) {
+ if (getHeaders(name).size() != 1) {
+ return false;
+ } else {
+ return pattern.matcher(getHeaders(name).get(0)).matches();
+ }
+ }
+
+ public void removeHeader(final String name) {
+ headers.remove(name.toLowerCase(Locale.ENGLISH));
+ }
+
+ public String getHeader(final String name) {
+ final HeaderField headerField = getHeaderField(name);
+
+ return (headerField == null) ? null : headerField.getValue();
+ }
+
+ public String getHeaderNotNull(final String name) {
+ final HeaderField headerField = getHeaderField(name);
+
+ return (headerField == null) ? "" : headerField.getValueNotNull();
+ }
+
+ public List<String> getHeaders(final String name) {
+ final HeaderField headerField = getHeaderField(name);
+
+ return (headerField == null) ? new ArrayList<String>() : headerField.getValues();
+ }
+
+ public HeaderField getHeaderField(final String name) {
+ return headers.get(name.toLowerCase(Locale.ENGLISH));
+ }
+
+ public int getLineNumber() {
+ return lineNumber;
+ }
+
+ public Map<String, String> toSingleMap() {
+ final Map<String, String> singleMap = new HashMap<String, String>();
+
+ for (final String key : headers.keySet()) {
+ HeaderField field = headers.get(key);
+ singleMap.put(field.getFieldName(), getHeader(key));
+ }
+
+ return singleMap;
+ }
+
+ public Map<String, List<String>> toMultiMap() {
+ final Map<String, List<String>> singleMap = new HashMap<String, List<String>>();
+
+ for (final String key : headers.keySet()) {
+ HeaderField field = headers.get(key);
+ singleMap.put(field.getFieldName(), field.getValues());
+ }
+
+ return singleMap;
+ }
+
+ private HeaderField getHeaderFieldOrDefault(final String name, final int lineNumber) {
+ HeaderField headerField = headers.get(name.toLowerCase(Locale.ENGLISH));
+
+ if (headerField == null) {
+ headerField = new HeaderField(name, lineNumber);
+ headers.put(name.toLowerCase(Locale.ENGLISH), headerField);
+ }
+
+ return headerField;
+ }
+
+ @Override
+ public Header clone() {
+ final Header newInstance = new Header(lineNumber);
+
+ for (final String key : headers.keySet()) {
+ newInstance.headers.put(key, headers.get(key).clone());
+ }
+
+ return newInstance;
+ }
+
+ public static List<String> splitValuesByComma(final String headerValue) {
+ final List<String> singleValues = new ArrayList<String>();
+
+ String[] parts = headerValue.split(",");
+ for (final String value : parts) {
+ singleValues.add(value.trim());
+ }
+
+ return singleValues;
+ }
+
+ public static class HeaderField implements Cloneable {
+ private final String fieldName;
+ private final List<String> values;
+ private final int lineNumber;
+
+ public HeaderField(final String fieldName, final int lineNumber) {
+ this(fieldName, new ArrayList<String>(), lineNumber);
+ }
+
+ public HeaderField(final String fieldName, final List<String> values, final int lineNumber) {
+ this.fieldName = fieldName;
+ this.values = values;
+ this.lineNumber = lineNumber;
+ }
+
+ public String getFieldName() {
+ return fieldName;
+ }
+
+ public List<String> getValues() {
+ return values;
+ }
+
+ public String getValue() {
+ final StringBuilder result = new StringBuilder();
+
+ for (final String value : values) {
+ result.append(value);
+ result.append(", ");
+ }
+
+ if (result.length() > 0) {
+ result.delete(result.length() - 2, result.length());
+ }
+
+ return result.toString();
+ }
+
+ public String getValueNotNull() {
+ final String value = getValue();
+
+ return (value == null) ? "" : value;
+ }
+
+ @Override
+ public HeaderField clone() {
+ List<String> newValues = new ArrayList<String>();
+ newValues.addAll(values);
+
+ return new HeaderField(fieldName, newValues, lineNumber);
+ }
+
+ public int getLineNumber() {
+ return lineNumber;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((fieldName == null) ? 0 : fieldName.hashCode());
+ result = prime * result + lineNumber;
+ result = prime * result + ((values == null) ? 0 : values.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ HeaderField other = (HeaderField) obj;
+ if (fieldName == null) {
+ if (other.fieldName != null) {
+ return false;
+ }
+ } else if (!fieldName.equals(other.fieldName)) {
+ return false;
+ }
+ if (lineNumber != other.lineNumber) {
+ return false;
+ }
+ if (values == null) {
+ if (other.values != null) {
+ return false;
+ }
+ } else if (!values.equals(other.values)) {
+ return false;
+ }
+ return true;
+ }
+ }
+}