You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by ch...@apache.org on 2014/11/18 16:12:21 UTC
[20/22] olingo-odata4 git commit: [OLINGO-472] Batch Refactoring
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchParserCommon.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchParserCommon.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchParserCommon.java
new file mode 100644
index 0000000..851a57b
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchParserCommon.java
@@ -0,0 +1,221 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.olingo.commons.api.http.HttpContentType;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.core.deserializer.batch.BufferedReaderIncludingLineEndings.Line;
+
+public class BatchParserCommon {
+
+ private static final String REG_EX_BOUNDARY =
+ "([a-zA-Z0-9_\\-\\.'\\+]{1,70})|\"([a-zA-Z0-9_\\-\\.'\\+\\s\\" +
+ "(\\),/:=\\?]{1,69}[a-zA-Z0-9_\\-\\.'\\+\\(\\),/:=\\?])\"";
+ private static final Pattern PATTERN_LAST_CRLF = Pattern.compile("(.*)(\r\n){1}( *)", Pattern.DOTALL);
+ private static final Pattern PATTERN_HEADER_LINE = Pattern.compile("([a-zA-Z\\-]+):\\s?(.*)\\s*");
+ private static final String REG_EX_APPLICATION_HTTP = "application/http";
+
+ public static final Pattern PATTERN_MULTIPART_BOUNDARY = Pattern.compile("multipart/mixed(.*)",
+ Pattern.CASE_INSENSITIVE);
+ public static final Pattern PATTERN_CONTENT_TYPE_APPLICATION_HTTP = Pattern.compile(REG_EX_APPLICATION_HTTP,
+ Pattern.CASE_INSENSITIVE);
+ public static final String BINARY_ENCODING = "binary";
+ public static final String HTTP_CONTENT_ID = "Content-Id";
+ public static final String HTTP_CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
+
+ public static final String HTTP_EXPECT = "Expect";
+ public static final String HTTP_FROM = "From";
+ public static final String HTTP_MAX_FORWARDS = "Max-Forwards";
+ public static final String HTTP_RANGE = "Range";
+ public static final String HTTP_TE = "TE";
+
+ public static String getBoundary(final String contentType, final int line) throws BatchException {
+ if (contentType.toLowerCase(Locale.ENGLISH).startsWith("multipart/mixed")) {
+ final String[] parameter = contentType.split(";");
+
+ for (final String pair : parameter) {
+
+ final String[] attrValue = pair.split("=");
+ if (attrValue.length == 2 && "boundary".equals(attrValue[0].trim().toLowerCase(Locale.ENGLISH))) {
+ if (attrValue[1].matches(REG_EX_BOUNDARY)) {
+ return trimQuota(attrValue[1].trim());
+ } else {
+ throw new BatchException("Invalid boundary format", BatchException.MessageKeys.INVALID_BOUNDARY, "" + line);
+ }
+ }
+
+ }
+ }
+ throw new BatchException("Content type is not multipart mixed",
+ BatchException.MessageKeys.INVALID_CONTENT_TYPE, HttpContentType.MULTIPART_MIXED);
+ }
+
+ public static String removeEndingSlash(String content) {
+ content = content.trim();
+ int lastSlashIndex = content.lastIndexOf('/');
+
+ return (lastSlashIndex == content.length() - 1) ? content.substring(0, content.length() - 1) : content;
+ }
+
+ private static String trimQuota(String boundary) {
+ if (boundary.matches("\".*\"")) {
+ boundary = boundary.replace("\"", "");
+ }
+
+ return boundary;
+ }
+
+ public static List<List<Line>> splitMessageByBoundary(final List<Line> message, final String boundary)
+ throws BatchException {
+ final List<List<Line>> messageParts = new LinkedList<List<Line>>();
+ List<Line> currentPart = new ArrayList<Line>();
+ boolean isEndReached = false;
+
+ final String quotedBoundary = Pattern.quote(boundary);
+ final Pattern boundaryDelimiterPattern = Pattern.compile("--" + quotedBoundary + "--[\\s ]*");
+ final Pattern boundaryPattern = Pattern.compile("--" + quotedBoundary + "[\\s ]*");
+
+ for (Line currentLine : message) {
+ if (boundaryDelimiterPattern.matcher(currentLine.toString()).matches()) {
+ removeEndingCRLFFromList(currentPart);
+ messageParts.add(currentPart);
+ isEndReached = true;
+ } else if (boundaryPattern.matcher(currentLine.toString()).matches()) {
+ removeEndingCRLFFromList(currentPart);
+ messageParts.add(currentPart);
+ currentPart = new LinkedList<Line>();
+ } else {
+ currentPart.add(currentLine);
+ }
+
+ if (isEndReached) {
+ break;
+ }
+ }
+
+ final int lineNumer = (message.size() > 0) ? message.get(0).getLineNumber() : 0;
+ // Remove preamble
+ if (messageParts.size() > 0) {
+ messageParts.remove(0);
+ }
+
+ if (!isEndReached) {
+ throw new BatchException("Missing close boundary delimiter", BatchException.MessageKeys.MISSING_CLOSE_DELIMITER,
+ "" + lineNumer);
+ }
+
+ return messageParts;
+ }
+
+ private static void removeEndingCRLFFromList(final List<Line> list) {
+ if (list.size() > 0) {
+ Line lastLine = list.remove(list.size() - 1);
+ list.add(removeEndingCRLF(lastLine));
+ }
+ }
+
+ public static Line removeEndingCRLF(final Line line) {
+ Pattern pattern = PATTERN_LAST_CRLF;
+ Matcher matcher = pattern.matcher(line.toString());
+
+ if (matcher.matches()) {
+ return new Line(matcher.group(1), line.getLineNumber());
+ } else {
+ return line;
+ }
+ }
+
+ public static Header consumeHeaders(final List<Line> remainingMessage) {
+ final int headerLineNumber = remainingMessage.size() != 0 ? remainingMessage.get(0).getLineNumber() : 0;
+ final Header headers = new Header(headerLineNumber);
+ final Iterator<Line> iter = remainingMessage.iterator();
+ Line currentLine;
+ boolean isHeader = true;
+
+ while (iter.hasNext() && isHeader) {
+ currentLine = iter.next();
+ final Matcher headerMatcher = PATTERN_HEADER_LINE.matcher(currentLine.toString());
+
+ if (headerMatcher.matches() && headerMatcher.groupCount() == 2) {
+ iter.remove();
+
+ String headerName = headerMatcher.group(1).trim();
+ String headerValue = headerMatcher.group(2).trim();
+
+ headers.addHeader(headerName, Header.splitValuesByComma(headerValue), currentLine.getLineNumber());
+ } else {
+ isHeader = false;
+ }
+ }
+
+ return headers;
+ }
+
+ public static void consumeBlankLine(final List<Line> remainingMessage, final boolean isStrict) throws BatchException {
+ //TODO is \r\n to strict?
+ if (remainingMessage.size() > 0 && remainingMessage.get(0).toString().matches("\\s*(\r\n|\n)\\s*")) {
+ remainingMessage.remove(0);
+ } else {
+ if (isStrict) {
+ final int lineNumber = (remainingMessage.size() > 0) ? remainingMessage.get(0).getLineNumber() : 0;
+ throw new BatchException("Missing blank line", BatchException.MessageKeys.MISSING_BLANK_LINE, "[None]", ""
+ + lineNumber);
+ }
+ }
+ }
+
+ public static InputStream convertLineListToInputStream(List<Line> messageList) {
+ final String message = lineListToString(messageList);
+
+ return new ByteArrayInputStream(message.getBytes());
+ }
+
+ private static String lineListToString(List<Line> messageList) {
+ final StringBuilder builder = new StringBuilder();
+
+ for (Line currentLine : messageList) {
+ builder.append(currentLine.toString());
+ }
+
+ return builder.toString();
+ }
+
+ public static String trimLineListToLength(final List<Line> list, final int length) {
+ final String message = lineListToString(list);
+ final int lastIndex = Math.min(length, message.length());
+
+ return (lastIndex > 0) ? message.substring(0, lastIndex) : "";
+ }
+
+ public static InputStream convertLineListToInputStream(List<Line> list, int length) {
+ final String message = trimLineListToLength(list, length);
+
+ return new ByteArrayInputStream(message.getBytes());
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchPart.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchPart.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchPart.java
new file mode 100644
index 0000000..9ee642d
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchPart.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+public interface BatchPart {
+ public Header getHeaders();
+
+ public boolean isStrict();
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchQueryOperation.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchQueryOperation.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchQueryOperation.java
new file mode 100644
index 0000000..faffd0f
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchQueryOperation.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.core.deserializer.batch.BufferedReaderIncludingLineEndings.Line;
+
+public class BatchQueryOperation implements BatchPart {
+
+ protected final boolean isStrict;
+ protected Line httpStatusLine;
+ protected Header headers;
+ protected List<Line> body;
+ protected int bodySize;
+ protected List<Line> message;
+
+ public BatchQueryOperation(final List<Line> message, final boolean isStrict) {
+ this.isStrict = isStrict;
+ this.message = message;
+ }
+
+ public BatchQueryOperation parse() throws BatchException {
+ httpStatusLine = consumeHttpStatusLine(message);
+ headers = BatchParserCommon.consumeHeaders(message);
+ BatchParserCommon.consumeBlankLine(message, isStrict);
+ body = message;
+
+ return this;
+ }
+
+ protected Line consumeHttpStatusLine(final List<Line> message) throws BatchException {
+ if (message.size() > 0 && !message.get(0).toString().trim().equals("")) {
+ final Line method = message.get(0);
+ message.remove(0);
+
+ return method;
+ } else {
+ final int line = (message.size() > 0) ? message.get(0).getLineNumber() : 0;
+ throw new BatchException("Missing http request line", BatchException.MessageKeys.INVALID_STATUS_LINE, "" + line);
+ }
+ }
+
+ public Line getHttpStatusLine() {
+ return httpStatusLine;
+ }
+
+ public List<Line> getBody() {
+ return body;
+ }
+
+ public int getBodySize() {
+ return bodySize;
+ }
+
+ @Override
+ public Header getHeaders() {
+ return headers;
+ }
+
+ @Override
+ public boolean isStrict() {
+ return isStrict;
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchRequestPartImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchRequestPartImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchRequestPartImpl.java
new file mode 100644
index 0000000..c68e130
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchRequestPartImpl.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.deserializer.batch.BatchRequestPart;
+
+/**
+ * Has to be immutable!
+ *
+ */
+public class BatchRequestPartImpl implements BatchRequestPart {
+
+ private List<ODataRequest> requests = new ArrayList<ODataRequest>();
+ private boolean isChangeSet;
+
+ public BatchRequestPartImpl(final boolean isChangeSet, final List<ODataRequest> requests) {
+ this.isChangeSet = isChangeSet;
+ this.requests = requests;
+ }
+
+ @Override
+ public boolean isChangeSet() {
+ return isChangeSet;
+ }
+
+ @Override
+ public List<ODataRequest> getRequests() {
+ return Collections.unmodifiableList(requests);
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchRequestTransformator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchRequestTransformator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchRequestTransformator.java
new file mode 100644
index 0000000..b169b9b
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchRequestTransformator.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.api.deserializer.batch.BatchDeserializerResult;
+import org.apache.olingo.server.core.deserializer.batch.HttpRequestStatusLine.ODataURI;
+
+public class BatchRequestTransformator implements BatchTransformator {
+ private final String baseUri;
+ private final String rawServiceResolutionUri;
+
+ public BatchRequestTransformator(final String baseUri, final String serviceResolutionUri) {
+ this.baseUri = baseUri;
+ this.rawServiceResolutionUri = serviceResolutionUri;
+ }
+
+ @Override
+ public List<BatchDeserializerResult> transform(final BatchBodyPart bodyPart) throws BatchException {
+ final List<ODataRequest> requests = new LinkedList<ODataRequest>();
+ final List<BatchDeserializerResult> resultList = new ArrayList<BatchDeserializerResult>();
+
+ validateBodyPartHeader(bodyPart);
+
+ for (BatchQueryOperation queryOperation : bodyPart.getRequests()) {
+ requests.add(processQueryOperation(bodyPart, baseUri, queryOperation));
+ }
+
+ resultList.add(new BatchRequestPartImpl(bodyPart.isChangeSet(), requests));
+ return resultList;
+ }
+
+ private ODataRequest
+ processQueryOperation(BatchBodyPart bodyPart, String baseUri, BatchQueryOperation queryOperation)
+ throws BatchException {
+ if (bodyPart.isChangeSet()) {
+ BatchQueryOperation encapsulatedQueryOperation = ((BatchChangeSetPart) queryOperation).getRequest();
+ handleContentId(queryOperation, encapsulatedQueryOperation);
+ validateHeader(queryOperation, true);
+
+ return createRequest(encapsulatedQueryOperation, baseUri, bodyPart.isChangeSet());
+ } else {
+ return createRequest(queryOperation, baseUri, bodyPart.isChangeSet());
+ }
+ }
+
+ private void handleContentId(BatchQueryOperation changeRequestPart, BatchQueryOperation request)
+ throws BatchException {
+ final HeaderField contentIdChangeRequestPart = getContentId(changeRequestPart);
+ final HeaderField contentIdRequest = getContentId(request);
+
+ if (contentIdChangeRequestPart == null && contentIdRequest == null) {
+ throw new BatchException("Missing content id", MessageKeys.MISSING_CONTENT_ID, changeRequestPart.getHeaders()
+ .getLineNumber());
+ } else if (contentIdChangeRequestPart != null) {
+ request.getHeaders().replaceHeaderField(contentIdChangeRequestPart);
+ }
+ }
+
+ private HeaderField getContentId(final BatchQueryOperation queryOperation) throws BatchException {
+ final HeaderField contentTypeHeader = queryOperation.getHeaders().getHeaderField(BatchParserCommon.HTTP_CONTENT_ID);
+
+ if (contentTypeHeader != null) {
+ if (contentTypeHeader.getValues().size() == 1) {
+ return contentTypeHeader;
+ } else {
+ throw new BatchException("Invalid header", MessageKeys.INVALID_HEADER, contentTypeHeader.getLineNumber());
+ }
+ }
+
+ return null;
+ }
+
+ private ODataRequest createRequest(BatchQueryOperation operation, String baseUri, boolean isChangeSet)
+ throws BatchException {
+ final HttpRequestStatusLine statusLine =
+ new HttpRequestStatusLine(operation.getHttpStatusLine(), baseUri, rawServiceResolutionUri, operation
+ .getHeaders());
+ statusLine.validateHttpMethod(isChangeSet);
+ final ODataURI uri = statusLine.getUri();
+
+ validateBody(statusLine, operation);
+ InputStream bodyStrean = getBodyStream(operation, statusLine);
+
+ validateForbiddenHeader(operation);
+
+ final ODataRequest request = new ODataRequest();
+ request.setBody(bodyStrean);
+ request.setMethod(statusLine.getMethod());
+ request.setRawBaseUri(uri.getRawBaseUri());
+ request.setRawODataPath(uri.getRawODataPath());
+ request.setRawQueryPath(uri.getRawQueryPath());
+ request.setRawRequestUri(uri.getRawRequestUri());
+ request.setRawServiceResolutionUri(uri.getRawServiceResolutionUri());
+
+ for (final HeaderField field : operation.getHeaders()) {
+ request.addHeader(field.getFieldName(), field.getValues());
+ }
+
+ return request;
+ }
+
+ private void validateForbiddenHeader(BatchQueryOperation operation) throws BatchException {
+ final Header header = operation.getHeaders();
+
+ if (header.exists(HttpHeader.AUTHORIZATION) || header.exists(BatchParserCommon.HTTP_EXPECT)
+ || header.exists(BatchParserCommon.HTTP_FROM) || header.exists(BatchParserCommon.HTTP_MAX_FORWARDS)
+ || header.exists(BatchParserCommon.HTTP_RANGE) || header.exists(BatchParserCommon.HTTP_TE)) {
+ throw new BatchException("Forbidden header", MessageKeys.FORBIDDEN_HEADER, header.getLineNumber());
+ }
+ }
+
+ private InputStream getBodyStream(BatchQueryOperation operation, HttpRequestStatusLine statusLine)
+ throws BatchException {
+ if (statusLine.getMethod().equals(HttpMethod.GET)) {
+ return new ByteArrayInputStream(new byte[0]);
+ } else {
+ int contentLength = BatchTransformatorCommon.getContentLength(operation.getHeaders());
+
+ if (contentLength == -1) {
+ return BatchParserCommon.convertLineListToInputStream(operation.getBody());
+ } else {
+ return BatchParserCommon.convertLineListToInputStream(operation.getBody(), contentLength);
+ }
+ }
+ }
+
+ private void validateBody(HttpRequestStatusLine statusLine, BatchQueryOperation operation) throws BatchException {
+ if (statusLine.getMethod().equals(HttpMethod.GET) && isUnvalidGetRequestBody(operation)) {
+ throw new BatchException("Invalid request line", MessageKeys.INVALID_CONTENT, statusLine.getLineNumber());
+ }
+ }
+
+ private boolean isUnvalidGetRequestBody(final BatchQueryOperation operation) {
+ return (operation.getBody().size() > 1)
+ || (operation.getBody().size() == 1 && !"".equals(operation.getBody().get(0).toString().trim()));
+ }
+
+ private void validateHeader(BatchPart bodyPart, boolean isChangeSet) throws BatchException {
+ final Header headers = bodyPart.getHeaders();
+
+ BatchTransformatorCommon.validateContentType(headers, BatchParserCommon.PATTERN_CONTENT_TYPE_APPLICATION_HTTP);
+ if (isChangeSet) {
+ BatchTransformatorCommon.validateContentTransferEncoding(headers);
+ }
+ }
+
+ private void validateBodyPartHeader(BatchBodyPart bodyPart) throws BatchException {
+ final Header header = bodyPart.getHeaders();
+
+ if (bodyPart.isChangeSet()) {
+ BatchTransformatorCommon.validateContentType(header, BatchParserCommon.PATTERN_MULTIPART_BOUNDARY);
+ } else {
+ BatchTransformatorCommon.validateContentTransferEncoding(header);
+ BatchTransformatorCommon.validateContentType(header, BatchParserCommon.PATTERN_CONTENT_TYPE_APPLICATION_HTTP);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchTransformator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchTransformator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchTransformator.java
new file mode 100644
index 0000000..462a2e2
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchTransformator.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.deserializer.batch.BatchDeserializerResult;
+
+public interface BatchTransformator {
+ public List<BatchDeserializerResult> transform(BatchBodyPart bodyPart) throws BatchException;
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchTransformatorCommon.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchTransformatorCommon.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchTransformatorCommon.java
new file mode 100644
index 0000000..4738641
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchTransformatorCommon.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.olingo.commons.api.http.HttpContentType;
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
+
+public class BatchTransformatorCommon {
+
+ public static void validateContentType(final Header headers, final Pattern pattern) throws BatchException {
+ List<String> contentTypes = headers.getHeaders(HttpHeader.CONTENT_TYPE);
+
+ if (contentTypes.size() == 0) {
+ throw new BatchException("Missing content type", MessageKeys.MISSING_CONTENT_TYPE, headers.getLineNumber());
+ }
+ if (!headers.isHeaderMatching(HttpHeader.CONTENT_TYPE, pattern)) {
+
+ throw new BatchException("Invalid content type", MessageKeys.INVALID_CONTENT_TYPE,
+ HttpContentType.MULTIPART_MIXED + " or " + HttpContentType.APPLICATION_HTTP);
+ }
+ }
+
+ public static void validateContentTransferEncoding(Header headers) throws BatchException {
+ final HeaderField contentTransferField = headers.getHeaderField(BatchParserCommon.HTTP_CONTENT_TRANSFER_ENCODING);
+
+ if (contentTransferField != null) {
+ final List<String> contentTransferValues = contentTransferField.getValues();
+ if (contentTransferValues.size() == 1) {
+ String encoding = contentTransferValues.get(0);
+
+ if (!BatchParserCommon.BINARY_ENCODING.equalsIgnoreCase(encoding)) {
+ throw new BatchException("Invalid content transfer encoding", MessageKeys.INVALID_CONTENT_TRANSFER_ENCODING,
+ headers.getLineNumber());
+ }
+ } else {
+ throw new BatchException("Invalid header", MessageKeys.INVALID_HEADER, headers.getLineNumber());
+ }
+ } else {
+ throw new BatchException("Missing mandatory content transfer encoding",
+ MessageKeys.MISSING_CONTENT_TRANSFER_ENCODING,
+ headers.getLineNumber());
+ }
+ }
+
+ public static int getContentLength(Header headers) throws BatchException {
+ final HeaderField contentLengthField = headers.getHeaderField(HttpHeader.CONTENT_LENGTH);
+
+ if (contentLengthField != null && contentLengthField.getValues().size() == 1) {
+ final List<String> contentLengthValues = contentLengthField.getValues();
+
+ try {
+ int contentLength = Integer.parseInt(contentLengthValues.get(0));
+
+ if (contentLength < 0) {
+ throw new BatchException("Invalid content length", MessageKeys.INVALID_CONTENT_LENGTH, contentLengthField
+ .getLineNumber());
+ }
+
+ return contentLength;
+ } catch (NumberFormatException e) {
+ throw new BatchException("Invalid header", MessageKeys.INVALID_HEADER, contentLengthField.getLineNumber());
+ }
+ }
+
+ return -1;
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BufferedReaderIncludingLineEndings.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BufferedReaderIncludingLineEndings.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BufferedReaderIncludingLineEndings.java
new file mode 100644
index 0000000..64b4bcb
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BufferedReaderIncludingLineEndings.java
@@ -0,0 +1,286 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BufferedReaderIncludingLineEndings extends Reader {
+ private static final char CR = '\r';
+ private static final char LF = '\n';
+ private static final int EOF = -1;
+ private static final int BUFFER_SIZE = 8192;
+ private Reader reader;
+ private char[] buffer;
+ private int offset = 0;
+ private int limit = 0;
+
+ public BufferedReaderIncludingLineEndings(final Reader reader) {
+ this(reader, BUFFER_SIZE);
+ }
+
+ public BufferedReaderIncludingLineEndings(final Reader reader, final int bufferSize) {
+ if (bufferSize <= 0) {
+ throw new IllegalArgumentException("Buffer size must be greater than zero.");
+ }
+
+ this.reader = reader;
+ buffer = new char[bufferSize];
+ }
+
+ @Override
+ public int read(final char[] charBuffer, final int bufferOffset, final int length) throws IOException {
+ if ((bufferOffset + length) > charBuffer.length) {
+ throw new IndexOutOfBoundsException("Buffer is too small");
+ }
+
+ if (length < 0 || bufferOffset < 0) {
+ throw new IndexOutOfBoundsException("Offset and length must be grater than zero");
+ }
+
+ // Check if buffer is filled. Return if EOF is reached
+ // Is buffer refill required
+ if (limit == offset || isEOF()) {
+ fillBuffer();
+
+ if (isEOF()) {
+ return EOF;
+ }
+ }
+
+ int bytesRead = 0;
+ int bytesToRead = length;
+ int currentOutputOffset = bufferOffset;
+
+ while (bytesToRead != 0) {
+ // Is buffer refill required?
+ if (limit == offset) {
+ fillBuffer();
+
+ if (isEOF()) {
+ bytesToRead = 0;
+ }
+ }
+
+ if (bytesToRead > 0) {
+ int readByte = Math.min(limit - offset, bytesToRead);
+ bytesRead += readByte;
+ bytesToRead -= readByte;
+
+ for (int i = 0; i < readByte; i++) {
+ charBuffer[currentOutputOffset++] = buffer[offset++];
+ }
+ }
+ }
+
+ return bytesRead;
+ }
+
+ public List<String> toList() throws IOException {
+ final List<String> result = new ArrayList<String>();
+ String currentLine;
+
+ while ((currentLine = readLine()) != null) {
+ result.add(currentLine);
+ }
+
+ return result;
+ }
+
+ public List<Line> toLineList() throws IOException {
+ final List<Line> result = new ArrayList<Line>();
+ String currentLine;
+ int counter = 1;
+
+ while ((currentLine = readLine()) != null) {
+ result.add(new Line(currentLine, counter++));
+ }
+
+ return result;
+ }
+
+ public String readLine() throws IOException {
+ if (limit == EOF) {
+ return null;
+ }
+
+ final StringBuilder stringBuffer = new StringBuilder();
+ boolean foundLineEnd = false; // EOF will be considered as line ending
+
+ while (!foundLineEnd) {
+ // Is buffer refill required?
+ if (limit == offset) {
+ if (fillBuffer() == EOF) {
+ foundLineEnd = true;
+ }
+ }
+
+ if (!foundLineEnd) {
+ char currentChar = buffer[offset++];
+ stringBuffer.append(currentChar);
+
+ if (currentChar == LF) {
+ foundLineEnd = true;
+ } else if (currentChar == CR) {
+ foundLineEnd = true;
+
+ // Check next char. Consume \n if available
+ // Is buffer refill required?
+ if (limit == offset) {
+ fillBuffer();
+ }
+
+ // Check if there is at least one character
+ if (limit != EOF && buffer[offset] == LF) {
+ stringBuffer.append(LF);
+ offset++;
+ }
+ }
+ }
+ }
+
+ return (stringBuffer.length() == 0) ? null : stringBuffer.toString();
+ }
+
+ @Override
+ public void close() throws IOException {
+ reader.close();
+ }
+
+ @Override
+ public boolean ready() throws IOException {
+ // Not EOF and buffer refill is not required
+ return !isEOF() && !(limit == offset);
+ }
+
+ @Override
+ public void reset() throws IOException {
+ throw new IOException("Reset is not supported");
+ }
+
+ @Override
+ public void mark(final int readAheadLimit) throws IOException {
+ throw new IOException("Mark is not supported");
+ }
+
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+ @Override
+ public long skip(final long n) throws IOException {
+ if (n == 0) {
+ return 0;
+ } else if (n < 0) {
+ throw new IllegalArgumentException("skip value is negative");
+ } else {
+ long charactersToSkip = n;
+ long charactersSkiped = 0;
+
+ while (charactersToSkip != 0) {
+ // Is buffer refill required?
+ if (limit == offset) {
+ fillBuffer();
+
+ if (isEOF()) {
+ charactersToSkip = 0;
+ }
+ }
+
+ // Check if more characters are available
+ if (!isEOF()) {
+ int skipChars = (int) Math.min(limit - offset, charactersToSkip);
+
+ charactersSkiped += skipChars;
+ charactersToSkip -= skipChars;
+ offset += skipChars;
+ }
+ }
+
+ return charactersSkiped;
+ }
+ }
+
+ private boolean isEOF() {
+ return limit == EOF;
+ }
+
+ private int fillBuffer() throws IOException {
+ limit = reader.read(buffer, 0, buffer.length);
+ offset = 0;
+
+ return limit;
+ }
+
+ public static class Line {
+ private final int lineNumber;
+ private final String content;
+
+ public Line(final String content, final int lineNumber) {
+ this.content = content;
+ this.lineNumber = lineNumber;
+ }
+
+ public int getLineNumber() {
+ return lineNumber;
+ }
+
+ @Override
+ public String toString() {
+ return content;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((content == null) ? 0 : content.hashCode());
+ result = prime * result + lineNumber;
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Line other = (Line) obj;
+ if (content == null) {
+ if (other.content != null) {
+ return false;
+ }
+ } else if (!content.equals(other.content)) {
+ return false;
+ }
+ if (lineNumber != other.lineNumber) {
+ return false;
+ }
+ return true;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/Header.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/Header.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/Header.java
new file mode 100644
index 0000000..a318201
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/Header.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+public class Header implements Iterable<HeaderField> {
+ private final Map<String, HeaderField> headers = new HashMap<String, HeaderField>();
+ private int lineNumber;
+
+ public Header(final int lineNumer) {
+ lineNumber = lineNumer;
+ }
+
+ public void addHeader(final String name, final String value, final int lineNumber) {
+ final HeaderField headerField = getHeaderFieldOrDefault(name, lineNumber);
+ final List<String> headerValues = headerField.getValues();
+
+ if (!headerValues.contains(value)) {
+ headerValues.add(value);
+ }
+ }
+
+ public void addHeader(final String name, final List<String> values, final int lineNumber) {
+ final HeaderField headerField = getHeaderFieldOrDefault(name, lineNumber);
+ final List<String> headerValues = headerField.getValues();
+
+ for (final String value : values) {
+ if (!headerValues.contains(value)) {
+ headerValues.add(value);
+ }
+ }
+ }
+
+ public void replaceHeaderField(final HeaderField headerField) {
+ headers.put(headerField.getFieldName().toLowerCase(Locale.ENGLISH), headerField);
+ }
+
+ public boolean exists(final String name) {
+ final HeaderField field = headers.get(name.toLowerCase(Locale.ENGLISH));
+
+ return field != null && field.getValues().size() != 0;
+ }
+
+ public boolean isHeaderMatching(final String name, final Pattern pattern) {
+ if (getHeaders(name).size() != 1) {
+ return false;
+ } else {
+ return pattern.matcher(getHeaders(name).get(0)).matches();
+ }
+ }
+
+ public void removeHeader(final String name) {
+ headers.remove(name.toLowerCase(Locale.ENGLISH));
+ }
+
+ public String getHeader(final String name) {
+ final HeaderField headerField = getHeaderField(name);
+
+ return (headerField == null) ? null : headerField.getValue();
+ }
+
+ public String getHeaderNotNull(final String name) {
+ final HeaderField headerField = getHeaderField(name);
+
+ return (headerField == null) ? "" : headerField.getValueNotNull();
+ }
+
+ public List<String> getHeaders(final String name) {
+ final HeaderField headerField = getHeaderField(name);
+
+ return (headerField == null) ? new ArrayList<String>() : headerField.getValues();
+ }
+
+ public HeaderField getHeaderField(final String name) {
+ return headers.get(name.toLowerCase(Locale.ENGLISH));
+ }
+
+ public int getLineNumber() {
+ return lineNumber;
+ }
+
+ public Map<String, String> toSingleMap() {
+ final Map<String, String> singleMap = new HashMap<String, String>();
+
+ for (final String key : headers.keySet()) {
+ HeaderField field = headers.get(key);
+ singleMap.put(field.getFieldName(), getHeader(key));
+ }
+
+ return singleMap;
+ }
+
+ public Map<String, List<String>> toMultiMap() {
+ final Map<String, List<String>> singleMap = new HashMap<String, List<String>>();
+
+ for (final String key : headers.keySet()) {
+ HeaderField field = headers.get(key);
+ singleMap.put(field.getFieldName(), field.getValues());
+ }
+
+ return singleMap;
+ }
+
+ private HeaderField getHeaderFieldOrDefault(final String name, final int lineNumber) {
+ HeaderField headerField = headers.get(name.toLowerCase(Locale.ENGLISH));
+
+ if (headerField == null) {
+ headerField = new HeaderField(name, lineNumber);
+ headers.put(name.toLowerCase(Locale.ENGLISH), headerField);
+ }
+
+ return headerField;
+ }
+
+ @Override
+ public Header clone() {
+ final Header newInstance = new Header(lineNumber);
+
+ for (final String key : headers.keySet()) {
+ newInstance.headers.put(key, headers.get(key).clone());
+ }
+
+ return newInstance;
+ }
+
+ @Override
+ public Iterator<HeaderField> iterator() {
+ return new Iterator<HeaderField>() {
+ Iterator<String> keyIterator = headers.keySet().iterator();
+
+ @Override
+ public boolean hasNext() {
+ return keyIterator.hasNext();
+ }
+
+ @Override
+ public HeaderField next() {
+ return headers.get(keyIterator.next());
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ public static List<String> splitValuesByComma(final String headerValue) {
+ final List<String> singleValues = new ArrayList<String>();
+
+ String[] parts = headerValue.split(",");
+ for (final String value : parts) {
+ singleValues.add(value.trim());
+ }
+
+ return singleValues;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/HeaderField.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/HeaderField.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/HeaderField.java
new file mode 100644
index 0000000..acd3231
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/HeaderField.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.util.ArrayList;
+import java.util.List;
+
+ public class HeaderField implements Cloneable {
+ private final String fieldName;
+ private final List<String> values;
+ private final int lineNumber;
+
+ public HeaderField(final String fieldName, final int lineNumber) {
+ this(fieldName, new ArrayList<String>(), lineNumber);
+ }
+
+ public HeaderField(final String fieldName, final List<String> values, final int lineNumber) {
+ this.fieldName = fieldName;
+ this.values = values;
+ this.lineNumber = lineNumber;
+ }
+
+ public String getFieldName() {
+ return fieldName;
+ }
+
+ public List<String> getValues() {
+ return values;
+ }
+
+ public String getValue() {
+ final StringBuilder result = new StringBuilder();
+
+ for (final String value : values) {
+ result.append(value);
+ result.append(", ");
+ }
+
+ if (result.length() > 0) {
+ result.delete(result.length() - 2, result.length());
+ }
+
+ return result.toString();
+ }
+
+ public String getValueNotNull() {
+ final String value = getValue();
+
+ return (value == null) ? "" : value;
+ }
+
+ @Override
+ public HeaderField clone() {
+ List<String> newValues = new ArrayList<String>();
+ newValues.addAll(values);
+
+ return new HeaderField(fieldName, newValues, lineNumber);
+ }
+
+ public int getLineNumber() {
+ return lineNumber;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((fieldName == null) ? 0 : fieldName.hashCode());
+ result = prime * result + lineNumber;
+ result = prime * result + ((values == null) ? 0 : values.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ HeaderField other = (HeaderField) obj;
+ if (fieldName == null) {
+ if (other.fieldName != null) {
+ return false;
+ }
+ } else if (!fieldName.equals(other.fieldName)) {
+ return false;
+ }
+ if (lineNumber != other.lineNumber) {
+ return false;
+ }
+ if (values == null) {
+ if (other.values != null) {
+ return false;
+ }
+ } else if (!values.equals(other.values)) {
+ return false;
+ }
+ return true;
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/HttpRequestStatusLine.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/HttpRequestStatusLine.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/HttpRequestStatusLine.java
new file mode 100644
index 0000000..1384770
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/HttpRequestStatusLine.java
@@ -0,0 +1,225 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+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.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.core.deserializer.batch.BufferedReaderIncludingLineEndings.Line;
+
+public class HttpRequestStatusLine {
+ private static final Pattern PATTERN_RELATIVE_URI = Pattern.compile("([^/][^?]*)(?:\\?(.*))?");
+ private static final Pattern PATTERN_ABSOLUTE_URI_WITH_HOST = Pattern.compile("(/[^?]*)(?:\\?(.*))?");
+ private static final Pattern PATTERN_ABSOLUTE_URI = Pattern.compile("(http[s]?://[^?]*)(?:\\?(.*))?");
+
+ private static final Set<String> HTTP_BATCH_METHODS = new HashSet<String>(Arrays.asList(new String[] { "GET" }));
+ private static final Set<String> HTTP_CHANGE_SET_METHODS = new HashSet<String>(Arrays.asList(new String[] { "POST",
+ "PUT", "DELETE", "PATCH" }));
+ private static final String HTTP_VERSION = "HTTP/1.1";
+
+ final private Line statusLine;
+ final String requestBaseUri;
+
+ private HttpMethod method;
+ private String httpVersion;
+ private Header header;
+ private ODataURI uri;
+
+ public HttpRequestStatusLine(final Line httpStatusLine, final String baseUri, final String serviceResolutionUri,
+ final Header requestHeader)
+ throws BatchException {
+ statusLine = httpStatusLine;
+ requestBaseUri = baseUri;
+ header = requestHeader;
+
+ parse();
+ }
+
+ private void parse() throws BatchException {
+ final String[] parts = statusLine.toString().split(" ");
+
+ if (parts.length == 3) {
+ method = parseMethod(parts[0]);
+ uri = new ODataURI(parts[1], requestBaseUri, statusLine.getLineNumber(), header.getHeaders(HttpHeader.HOST));
+ httpVersion = parseHttpVersion(parts[2]);
+ } else {
+ throw new BatchException("Invalid status line", MessageKeys.INVALID_STATUS_LINE, statusLine.getLineNumber());
+ }
+ }
+
+ private HttpMethod parseMethod(final String method) throws BatchException {
+ try {
+ return HttpMethod.valueOf(method.trim());
+ } catch (IllegalArgumentException e) {
+ throw new BatchException("Illegal http method", MessageKeys.INVALID_METHOD, statusLine.getLineNumber());
+ }
+ }
+
+ private String parseHttpVersion(final String httpVersion) throws BatchException {
+ if (!HTTP_VERSION.equals(httpVersion.trim())) {
+ throw new BatchException("Invalid http version", MessageKeys.INVALID_HTTP_VERSION, statusLine.getLineNumber());
+ } else {
+ return HTTP_VERSION;
+ }
+ }
+
+ public void validateHttpMethod(boolean isChangeSet) throws BatchException {
+ Set<String> validMethods = (isChangeSet) ? HTTP_CHANGE_SET_METHODS : HTTP_BATCH_METHODS;
+
+ if (!validMethods.contains(getMethod().toString())) {
+ if (isChangeSet) {
+ throw new BatchException("Invalid change set method", MessageKeys.INVALID_CHANGESET_METHOD, statusLine
+ .getLineNumber());
+ } else {
+ throw new BatchException("Invalid query operation method", MessageKeys.INVALID_QUERY_OPERATION_METHOD,
+ statusLine.getLineNumber());
+ }
+ }
+ }
+
+ public HttpMethod getMethod() {
+ return method;
+ }
+
+ public String getHttpVersion() {
+ return httpVersion;
+ }
+
+ public int getLineNumber() {
+ return statusLine.getLineNumber();
+ }
+
+ public ODataURI getUri() {
+ return uri;
+ }
+
+ public static class ODataURI {
+ private String rawServiceResolutionUri;
+ private String rawQueryPath;
+ private String rawODataPath;
+ private String rawBaseUri;
+ private String rawRequestUri;
+ private final String requestBaseUri;
+ private final int lineNumber;
+
+ public ODataURI(final String rawUri, String requestBaseUri) throws BatchException {
+ this(rawUri, requestBaseUri, 0, new ArrayList<String>());
+ }
+
+ public ODataURI(final String rawUri, String requestBaseUri, int lineNumber, List<String> hostHeader)
+ throws BatchException {
+ this.lineNumber = lineNumber;
+ this.requestBaseUri = requestBaseUri;
+
+ final Matcher absoluteUriMatcher = PATTERN_ABSOLUTE_URI.matcher(rawUri);
+ final Matcher absoluteUriWtithHostMatcher = PATTERN_ABSOLUTE_URI_WITH_HOST.matcher(rawUri);
+ final Matcher relativeUriMatcher = PATTERN_RELATIVE_URI.matcher(rawUri);
+
+ if (absoluteUriMatcher.matches()) {
+ buildUri(absoluteUriMatcher.group(1), absoluteUriMatcher.group(2));
+
+ } else if (absoluteUriWtithHostMatcher.matches()) {
+ if (hostHeader != null && hostHeader.size() == 1) {
+ buildUri(hostHeader.get(0) + absoluteUriWtithHostMatcher.group(1), absoluteUriWtithHostMatcher.group(2));
+ } else {
+ throw new BatchException("Exactly one host header is required", MessageKeys.MISSING_MANDATORY_HEADER,
+ lineNumber);
+ }
+
+ } else if (relativeUriMatcher.matches()) {
+ buildUri(requestBaseUri + "/" + relativeUriMatcher.group(1), relativeUriMatcher.group(2));
+
+ } else {
+ throw new BatchException("Invalid uri", MessageKeys.INVALID_URI, lineNumber);
+ }
+ }
+
+ private void buildUri(final String resourceUri, final String queryOptions) throws BatchException {
+ if (!resourceUri.startsWith(requestBaseUri)) {
+ throw new BatchException("Host do not match", MessageKeys.INVALID_URI, lineNumber);
+ }
+
+ final int oDataPathIndex = resourceUri.indexOf(requestBaseUri);
+
+ rawBaseUri = requestBaseUri;
+ rawODataPath = resourceUri.substring(oDataPathIndex + requestBaseUri.length());
+ rawRequestUri = requestBaseUri + rawODataPath;
+
+ if (queryOptions != null) {
+ rawRequestUri += "?" + queryOptions;
+ rawQueryPath = queryOptions;
+ } else {
+ rawQueryPath = "";
+ }
+ }
+
+ public String getRawServiceResolutionUri() {
+ return rawServiceResolutionUri;
+ }
+
+ public void setRawServiceResolutionUri(String rawServiceResolutionUri) {
+ this.rawServiceResolutionUri = rawServiceResolutionUri;
+ }
+
+ public String getRawQueryPath() {
+ return rawQueryPath;
+ }
+
+ public void setRawQueryPath(String rawQueryPath) {
+ this.rawQueryPath = rawQueryPath;
+ }
+
+ public String getRawODataPath() {
+ return rawODataPath;
+ }
+
+ public void setRawODataPath(String rawODataPath) {
+ this.rawODataPath = rawODataPath;
+ }
+
+ public String getRawBaseUri() {
+ return rawBaseUri;
+ }
+
+ public void setRawBaseUri(String rawBaseUri) {
+ this.rawBaseUri = rawBaseUri;
+ }
+
+ public String getRawRequestUri() {
+ return rawRequestUri;
+ }
+
+ public void setRawRequestUri(String rawRequestUri) {
+ this.rawRequestUri = rawRequestUri;
+ }
+
+ public String getRequestBaseUri() {
+ return requestBaseUri;
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/BatchResponseSerializer.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/BatchResponseSerializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/BatchResponseSerializer.java
new file mode 100644
index 0000000..6f7255c
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/BatchResponseSerializer.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.serializer;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.olingo.commons.api.http.HttpContentType;
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpStatusCode;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.api.deserializer.batch.ODataResponsePart;
+import org.apache.olingo.server.core.deserializer.batch.BatchParserCommon;
+import org.apache.olingo.server.core.serializer.utils.CircleStreamBuffer;
+
+public class BatchResponseSerializer {
+ private static final int BUFFER_SIZE = 4096;
+ private static final String DOUBLE_DASH = "--";
+ private static final String COLON = ":";
+ private static final String SP = " ";
+ private static final String CRLF = "\r\n";
+
+ public void toODataResponse(final List<ODataResponsePart> batchResponse, final ODataResponse response)
+ throws IOException, BatchException {
+ final String boundary = generateBoundary("batch");
+
+ setStatusCode(response);
+ ResponseWriter writer = createBody(batchResponse, boundary);
+
+ response.setContent(writer.toInputStream());
+ setHttpHeader(response, writer, boundary);
+ }
+
+ private ResponseWriter createBody(final List<ODataResponsePart> batchResponses, final String boundary)
+ throws IOException, BatchException {
+ final ResponseWriter writer = new ResponseWriter();
+
+ for (final ODataResponsePart part : batchResponses) {
+ writer.append(getDashBoundary(boundary));
+
+ if (part.isChangeSet()) {
+ appendChangeSet(part, writer);
+ } else {
+ appendBodyPart(part.getResponses().get(0), writer, false);
+ }
+ }
+ writer.append(getCloseDelimiter(boundary));
+
+ return writer;
+ }
+
+ private void appendChangeSet(ODataResponsePart part, ResponseWriter writer) throws IOException, BatchException {
+ final String changeSetBoundary = generateBoundary("changeset");
+
+ appendChangeSetHeader(writer, changeSetBoundary);
+ writer.append(CRLF);
+
+ for (final ODataResponse response : part.getResponses()) {
+ writer.append(getDashBoundary(changeSetBoundary));
+ appendBodyPart(response, writer, true);
+ }
+
+ writer.append(getCloseDelimiter(changeSetBoundary));
+ writer.append(CRLF);
+ }
+
+ private void appendBodyPart(ODataResponse response, ResponseWriter writer, boolean isChangeSet) throws IOException,
+ BatchException {
+ byte[] body = getBody(response);
+
+ appendBodyPartHeader(response, writer, isChangeSet);
+ writer.append(CRLF);
+
+ appendStatusLine(response, writer);
+ appendResponseHeader(response, body.length, writer);
+ writer.append(CRLF);
+
+ writer.append(body);
+ writer.append(CRLF);
+ }
+
+ private byte[] getBody(final ODataResponse response) throws IOException {
+ final InputStream content = response.getContent();
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ if (content != null) {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int n;
+
+ while ((n = content.read(buffer, 0, buffer.length)) != -1) {
+ out.write(buffer, 0, n);
+ }
+ out.flush();
+
+ return out.toByteArray();
+ } else {
+ return new byte[0];
+ }
+ }
+
+ private void appendChangeSetHeader(ResponseWriter writer, final String changeSetBoundary) throws IOException {
+ appendHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED.toString() + "; boundary="
+ + changeSetBoundary, writer);
+ }
+
+ private void appendHeader(String name, String value, ResponseWriter writer) throws IOException {
+ writer.append(name)
+ .append(COLON)
+ .append(SP)
+ .append(value)
+ .append(CRLF);
+ }
+
+ private void appendStatusLine(ODataResponse response, ResponseWriter writer) throws IOException {
+ writer.append("HTTP/1.1")
+ .append(SP)
+ .append("" + response.getStatusCode())
+ .append(SP)
+ .append(HttpStatusCode.fromStatusCode(response.getStatusCode()).toString())
+ .append(CRLF);
+ }
+
+ private void appendResponseHeader(ODataResponse response, int contentLength, ResponseWriter writer)
+ throws IOException {
+ final Map<String, String> header = response.getHeaders();
+
+ for (final String key : header.keySet()) {
+ // Requests do never has a content id header
+ if (!key.equalsIgnoreCase(BatchParserCommon.HTTP_CONTENT_ID)) {
+ appendHeader(key, header.get(key), writer);
+ }
+ }
+
+ appendHeader(HttpHeader.CONTENT_LENGTH, "" + contentLength, writer);
+ }
+
+ private void appendBodyPartHeader(ODataResponse response, ResponseWriter writer, boolean isChangeSet)
+ throws BatchException, IOException {
+ appendHeader(HttpHeader.CONTENT_TYPE, HttpContentType.APPLICATION_HTTP, writer);
+ appendHeader(BatchParserCommon.HTTP_CONTENT_TRANSFER_ENCODING, BatchParserCommon.BINARY_ENCODING, writer);
+
+ if (isChangeSet) {
+ if (response.getHeaders().get(BatchParserCommon.HTTP_CONTENT_ID) != null) {
+ appendHeader(BatchParserCommon.HTTP_CONTENT_ID, response.getHeaders().get(BatchParserCommon.HTTP_CONTENT_ID),
+ writer);
+ } else {
+ throw new BatchException("Missing content id", MessageKeys.MISSING_CONTENT_ID, "");
+ }
+ }
+ }
+
+ private void setHttpHeader(ODataResponse response, ResponseWriter writer, final String boundary) {
+ response.setHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED.toString() + "; boundary=" + boundary);
+ response.setHeader(HttpHeader.CONTENT_LENGTH, "" + writer.length());
+ }
+
+ private void setStatusCode(final ODataResponse response) {
+ response.setStatusCode(HttpStatusCode.ACCEPTED.getStatusCode());
+ }
+
+ private String getDashBoundary(String boundary) {
+ return DOUBLE_DASH + boundary + CRLF;
+ }
+
+ private String getCloseDelimiter(final String boundary) {
+ return DOUBLE_DASH + boundary + DOUBLE_DASH + CRLF;
+ }
+
+ private String generateBoundary(final String value) {
+ return value + "_" + UUID.randomUUID().toString();
+ }
+
+ private static class ResponseWriter {
+ private CircleStreamBuffer buffer = new CircleStreamBuffer();
+ private BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(buffer.getOutputStream()));
+ private int length = 0;
+
+ public ResponseWriter append(final String content) throws IOException {
+ length += content.length();
+ writer.write(content);
+
+ return this;
+ }
+
+ public ResponseWriter append(final byte[] content) throws IOException {
+ length += content.length;
+ writer.flush();
+ buffer.getOutputStream().write(content, 0, content.length);
+
+ return this;
+ }
+
+ public int length() {
+ return length;
+ }
+
+ public InputStream toInputStream() throws IOException {
+ writer.flush();
+ writer.close();
+
+ return buffer.getInputStream();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/FixedFormatSerializerImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/FixedFormatSerializerImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/FixedFormatSerializerImpl.java
index 01951ae..e165b75 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/FixedFormatSerializerImpl.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/FixedFormatSerializerImpl.java
@@ -19,11 +19,16 @@
package org.apache.olingo.server.core.serializer;
import java.io.ByteArrayInputStream;
+import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
+import java.util.List;
import org.apache.olingo.commons.api.edm.EdmPrimitiveType;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.deserializer.batch.ODataResponsePart;
import org.apache.olingo.server.api.serializer.FixedFormatSerializer;
import org.apache.olingo.server.api.serializer.PrimitiveValueSerializerOptions;
import org.apache.olingo.server.api.serializer.SerializerException;
@@ -56,4 +61,12 @@ public class FixedFormatSerializerImpl implements FixedFormatSerializer {
throw new SerializerException("Encoding exception.", e, SerializerException.MessageKeys.IO_EXCEPTION);
}
}
+
+ //TODO: Signature
+ @Override
+ public void writeResponseParts(List<ODataResponsePart> batchResponses, ODataResponse response) throws BatchException,
+ IOException {
+ BatchResponseSerializer writer = new BatchResponseSerializer();
+ writer.toODataResponse(batchResponses, response);
+ }
}