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:09 UTC
[08/22] olingo-odata4 git commit: MockedBatchHandler Test
MockedBatchHandler Test
Signed-off-by: Christian Amend <ch...@apache.org>
Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/0bd32951
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/0bd32951
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/0bd32951
Branch: refs/heads/master
Commit: 0bd32951b8ebcfce895fa5d5eb8fd76954d6bfd3
Parents: ee2451c
Author: Christian Holzer <c....@sap.com>
Authored: Mon Nov 3 17:16:47 2014 +0100
Committer: Christian Amend <ch...@apache.org>
Committed: Thu Nov 13 17:10:56 2014 +0100
----------------------------------------------------------------------
.../olingo/server/api/batch/BatchOperation.java | 2 +-
.../server/api/processor/BatchProcessor.java | 2 +-
.../batch/handler/BatchChangeSetSorter.java | 41 +-
.../core/batch/handler/BatchOperationImpl.java | 2 +-
.../core/batch/handler/BatchPartHandler.java | 56 +-
.../BatchRequestTransformator.java | 17 +-
.../transformator/BatchTransformatorCommon.java | 159 -----
.../transformator/HttpRequestStatusLine.java | 229 ++++++++
.../batch/handler/BatchChangeSetSorterTest.java | 4 +-
.../batch/handler/MockedBatchHandlerTest.java | 582 +++++++++++++++++++
10 files changed, 894 insertions(+), 200 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
index af7d9c1..2d09a94 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
@@ -27,7 +27,7 @@ import org.apache.olingo.server.api.ODataResponse;
public interface BatchOperation {
public List<BatchRequestPart> parseBatchRequest(InputStream in) throws BatchException;
- public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart);
+ public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) throws BatchException;
public ODataResponsePart handleBatchRequest(BatchRequestPart request) throws BatchException;
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
index 221b38a..a0a7135 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
@@ -26,7 +26,7 @@ import org.apache.olingo.server.api.batch.BatchOperation;
import org.apache.olingo.server.api.batch.BatchRequestPart;
public interface BatchProcessor extends Processor {
- void executeBatch(BatchOperation operation, ODataRequest requests, ODataResponse response);
+ void executeBatch(BatchOperation operation, ODataRequest request, ODataResponse response);
List<ODataResponse> executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
BatchRequestPart requestPart);
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
index c348512..f8ac653 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
@@ -39,7 +39,7 @@ public class BatchChangeSetSorter {
private static Pattern referencePattern = Pattern.compile(REG_EX_REFERENCE);
private Set<String> knownContentId = new HashSet<String>();
- private Map<String, List<ODataRequest>> requestContentIdMapping = new HashMap<String, List<ODataRequest>>();
+ private Map<String, List<ODataRequest>> requestReferenceMapping = new HashMap<String, List<ODataRequest>>();
public BatchChangeSetSorter(List<ODataRequest> requests) throws BatchException {
sort(requests);
@@ -50,9 +50,12 @@ public class BatchChangeSetSorter {
}
private List<ODataRequest> sort(final List<ODataRequest> requests) throws BatchException {
- extractUrlContentId(requests);
- orderdList.addAll(getRequestsWithoutReferences());
-
+ extractUrlReference(requests);
+
+ final List<ODataRequest> requestsWithoutReferences = getRequestsWithoutReferences();
+ orderdList.addAll(requestsWithoutReferences);
+ addRequestsToKnownContentIds(requestsWithoutReferences);
+
boolean areRequestsProcessed = true;
while (requestsToProcessAvailable() && areRequestsProcessed) {
areRequestsProcessed = processRemainingRequests(orderdList);
@@ -66,7 +69,7 @@ public class BatchChangeSetSorter {
}
private boolean requestsToProcessAvailable() {
- return requestContentIdMapping.keySet().size() != 0;
+ return requestReferenceMapping.keySet().size() != 0;
}
private boolean processRemainingRequests(List<ODataRequest> orderdList) {
@@ -81,10 +84,10 @@ public class BatchChangeSetSorter {
List<ODataRequest> result = new ArrayList<ODataRequest>();
for (String contextId : knownContentId) {
- List<ODataRequest> processedRequests = requestContentIdMapping.get(contextId);
+ List<ODataRequest> processedRequests = requestReferenceMapping.get(contextId);
if (processedRequests != null && processedRequests.size() != 0) {
result.addAll(processedRequests);
- requestContentIdMapping.remove(contextId);
+ requestReferenceMapping.remove(contextId);
}
}
@@ -92,10 +95,9 @@ public class BatchChangeSetSorter {
}
private List<ODataRequest> getRequestsWithoutReferences() {
- final List<ODataRequest> requestsWithoutReference = requestContentIdMapping.get(null);
- requestContentIdMapping.remove(null);
+ final List<ODataRequest> requestsWithoutReference = requestReferenceMapping.get(null);
+ requestReferenceMapping.remove(null);
- addRequestsToKnownContentIds(requestsWithoutReference);
return requestsWithoutReference;
}
@@ -112,33 +114,38 @@ public class BatchChangeSetSorter {
return request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
}
- private void extractUrlContentId(List<ODataRequest> requests) {
+ private void extractUrlReference(List<ODataRequest> requests) {
for (ODataRequest request : requests) {
final String reference = getReferenceInURI(request);
- addRequestToMapping(reference, request);
+ addRequestToReferenceMapping(reference, request);
}
}
- private void addRequestToMapping(final String reference, final ODataRequest request) {
- List<ODataRequest> requestList = requestContentIdMapping.get(reference);
+ private void addRequestToReferenceMapping(final String reference, final ODataRequest request) {
+ List<ODataRequest> requestList = requestReferenceMapping.get(reference);
requestList = (requestList == null) ? new ArrayList<ODataRequest>() : requestList;
requestList.add(request);
- requestContentIdMapping.put(reference, requestList);
+ requestReferenceMapping.put(reference, requestList);
}
public static String getReferenceInURI(ODataRequest request) {
- Matcher matcher = referencePattern.matcher(removeFollingPathSegments(request.getRawODataPath()));
+ Matcher matcher = referencePattern.matcher(removeFollingPathSegments(removeFirstSplash(request.getRawODataPath())));
return (matcher.matches()) ? matcher.group(1) : null;
}
+ private static String removeFirstSplash(String rawODataPath) {
+ final int indexOfSlash = rawODataPath.indexOf("/");
+ return (indexOfSlash == 0) ? rawODataPath.substring(1) : rawODataPath;
+ }
+
private static String removeFollingPathSegments(String rawODataPath) {
final int indexOfSlash = rawODataPath.indexOf("/");
return (indexOfSlash != -1) ? rawODataPath.substring(0, indexOfSlash) : rawODataPath;
}
public static void replaceContentIdReference(ODataRequest request, String contentId, String resourceUri) {
- final String newUri = request.getRawODataPath().replace("$" + contentId, resourceUri);
+ final String newUri = request.getRawODataPath().replace("/$" + contentId, resourceUri);
request.setRawODataPath(newUri);
request.setRawRequestUri(request.getRawBaseUri() + "/" + newUri);
}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
index 1f5e243..cf7ad7c 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
@@ -53,7 +53,7 @@ public class BatchOperationImpl implements BatchOperation {
}
@Override
- public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) {
+ public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) throws BatchException {
return partHandler.handleODataRequest(request, requestPart);
}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
index df23c55..c678355 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
@@ -23,7 +23,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import org.apache.olingo.commons.api.ODataRuntimeException;
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.ODataResponse;
import org.apache.olingo.server.api.batch.BatchException;
@@ -33,6 +35,7 @@ import org.apache.olingo.server.api.batch.ODataResponsePart;
import org.apache.olingo.server.api.processor.BatchProcessor;
import org.apache.olingo.server.core.ODataHandler;
import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
+import org.apache.olingo.server.core.batch.transformator.HttpRequestStatusLine.ODataURI;
public class BatchPartHandler {
@@ -48,21 +51,18 @@ public class BatchPartHandler {
this.batchOperation = batchOperation;
}
- public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) {
+ public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) throws BatchException {
final ODataResponse response;
if(requestPart.isChangeSet()) {
- final UriMapping mapping = getUriMappingOrDefault(requestPart);
- final String reference = BatchChangeSetSorter.getReferenceInURI(request);
- if(reference != null) {
- BatchChangeSetSorter.replaceContentIdReference(request, reference, mapping.getUri(reference));
- }
-
- response = oDataHandler.process(request);
+ final UriMapping mapping = replaceReference(request, requestPart);
+
+ response = oDataHandler.process(request);
+ final String resourceUri = getODataPath(request, response);
final String contentId = request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
- final String locationHeader = request.getHeader(HttpHeader.LOCATION);
- mapping.addMapping(contentId, locationHeader);
+
+ mapping.addMapping(contentId, resourceUri);
} else {
response = oDataHandler.process(request);
}
@@ -74,11 +74,45 @@ public class BatchPartHandler {
return response;
}
+
+ private String getODataPath(ODataRequest request, ODataResponse response) throws BatchException {
+ String resourceUri = null;
+
+ if(request.getMethod() == HttpMethod.POST) {
+ // Create entity
+ // The URI of the new resource will be generated by the server and published in the location header
+ ODataURI uri = new ODataURI(response.getHeaders().get(HttpHeader.LOCATION), request.getRawBaseUri());
+ resourceUri = uri.getRawODataPath();
+ } else {
+ // Update, Upsert (PUT, PATCH, Delete)
+ // These methods still addresses a given URI, so we use the URI given by the request
+ resourceUri = request.getRawODataPath();
+ }
+
+ return resourceUri;
+ }
+
+ private UriMapping replaceReference(ODataRequest request, BatchRequestPart requestPart) {
+ final UriMapping mapping = getUriMappingOrDefault(requestPart);
+ final String reference = BatchChangeSetSorter.getReferenceInURI(request);
+
+ if(reference != null) {
+ final String replacement = mapping.getUri(reference);
+
+ if(replacement != null) {
+ BatchChangeSetSorter.replaceContentIdReference(request, reference, replacement);
+ } else {
+ throw new ODataRuntimeException("Required Content-Id for reference \"" + reference + "\" not found.");
+ }
+ }
+
+ return mapping;
+ }
private UriMapping getUriMappingOrDefault(final BatchRequestPart requestPart) {
UriMapping mapping = uriMapping.get(requestPart);
- if(uriMapping == null) {
+ if(mapping == null) {
mapping = new UriMapping();
}
uriMapping.put(requestPart, mapping);
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
index 94d5226..0bf0104 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
@@ -38,7 +38,7 @@ import org.apache.olingo.server.core.batch.parser.BatchQueryOperation;
import org.apache.olingo.server.core.batch.parser.BatchRequestPartImpl;
import org.apache.olingo.server.core.batch.parser.Header;
import org.apache.olingo.server.core.batch.parser.HeaderField;
-import org.apache.olingo.server.core.batch.transformator.BatchTransformatorCommon.HttpRequestStatusLine;
+import org.apache.olingo.server.core.batch.transformator.HttpRequestStatusLine.ODataURI;
public class BatchRequestTransformator implements BatchTransformator {
private final String baseUri;
@@ -111,20 +111,21 @@ public class BatchRequestTransformator implements BatchTransformator {
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(statusLine.getRawBaseUri());
- request.setRawODataPath(statusLine.getRawODataPath());
- request.setRawQueryPath(statusLine.getRawQueryPath());
- request.setRawRequestUri(statusLine.getRawRequestUri());
- request.setRawServiceResolutionUri(statusLine.getRawServiceResolutionUri());
+ 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());
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
index c9da563..fc2035a 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
@@ -18,20 +18,14 @@
*/
package org.apache.olingo.server.core.batch.transformator;
-import java.util.Arrays;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.olingo.commons.api.http.HttpContentType;
import org.apache.olingo.commons.api.http.HttpHeader;
-import org.apache.olingo.commons.api.http.HttpMethod;
import org.apache.olingo.server.api.batch.BatchException;
import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
-import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
import org.apache.olingo.server.core.batch.parser.Header;
import org.apache.olingo.server.core.batch.parser.HeaderField;
@@ -94,157 +88,4 @@ public class BatchTransformatorCommon {
return -1;
}
-
- public static class HttpRequestStatusLine {
- private static final Pattern PATTERN_RELATIVE_URI = Pattern.compile("([^/][^?]*)(?:\\?(.*))?");
- private static final Pattern PATTERN_ABSOLUTE_URI_WITH_HOST = Pattern.compile("(/[^?]*)(?:\\?(.*))?");
- private static final Pattern PATTERN_ABSOLUTE_URI = Pattern.compile("(http[s]?://[^?]*)(?:\\?(.*))?");
-
- private static final Set<String> HTTP_BATCH_METHODS = new HashSet<String>(Arrays.asList(new String[] { "GET" }));
- private static final Set<String> HTTP_CHANGE_SET_METHODS = new HashSet<String>(Arrays.asList(new String[] { "POST",
- "PUT", "DELETE", "MERGE", "PATCH" }));
- private static final String HTTP_VERSION = "HTTP/1.1";
-
- final private Line statusLine;
- final String requestBaseUri;
-
- private HttpMethod method;
- private String httpVersion;
- private String rawServiceResolutionUri;
- private String rawQueryPath;
- private String rawODataPath;
- private String rawBaseUri;
- private Header header;
- private String rawRequestUri;
-
- public HttpRequestStatusLine(final Line httpStatusLine, final String baseUri, final String serviceResolutionUri,
- final Header requestHeader)
- throws BatchException {
- statusLine = httpStatusLine;
- requestBaseUri = baseUri;
- header = requestHeader;
- rawServiceResolutionUri = serviceResolutionUri;
-
- parse();
- }
-
- private void parse() throws BatchException {
- final String[] parts = statusLine.toString().split(" ");
-
- if (parts.length == 3) {
- method = parseMethod(parts[0]);
- parseRawPaths(parts[1]);
- httpVersion = parseHttpVersion(parts[2]);
- } else {
- throw new BatchException("Invalid status line", MessageKeys.INVALID_STATUS_LINE, statusLine.getLineNumber());
- }
- }
-
- private HttpMethod parseMethod(final String method) throws BatchException {
- try {
- return HttpMethod.valueOf(method.trim());
- } catch (IllegalArgumentException e) {
- throw new BatchException("Illegal http method", MessageKeys.INVALID_METHOD, statusLine.getLineNumber());
- }
- }
-
- private void parseRawPaths(final String rawUrl) throws BatchException {
- final Matcher absoluteUriMatcher = PATTERN_ABSOLUTE_URI.matcher(rawUrl);
- final Matcher absoluteUriWtithHostMatcher = PATTERN_ABSOLUTE_URI_WITH_HOST.matcher(rawUrl);
- final Matcher relativeUriMatcher = PATTERN_RELATIVE_URI.matcher(rawUrl);
-
- if (absoluteUriMatcher.matches()) {
- buildUri(absoluteUriMatcher.group(1), absoluteUriMatcher.group(2));
-
- } else if (absoluteUriWtithHostMatcher.matches()) {
- final List<String> hostHeader = header.getHeaders(HttpHeader.HOST);
- if (hostHeader.size() == 1) {
- buildUri(hostHeader.get(0) + absoluteUriWtithHostMatcher.group(1), absoluteUriWtithHostMatcher.group(2));
- } else {
- throw new BatchException("Exactly one host header is required", MessageKeys.MISSING_MANDATORY_HEADER,
- statusLine.getLineNumber());
- }
-
- } else if (relativeUriMatcher.matches()) {
- buildUri(requestBaseUri + "/" + relativeUriMatcher.group(1), relativeUriMatcher.group(2));
-
- } else {
- throw new BatchException("Invalid uri", MessageKeys.INVALID_URI, statusLine.getLineNumber());
- }
- }
-
- private void buildUri(final String resourceUri, final String queryOptions) throws BatchException {
- if(!resourceUri.startsWith(requestBaseUri)) {
- throw new BatchException("Host do not match", MessageKeys.INVALID_URI, statusLine.getLineNumber());
- }
-
- final int oDataPathIndex = resourceUri.indexOf(requestBaseUri);
-
- rawBaseUri = requestBaseUri;
- rawODataPath = resourceUri.substring(oDataPathIndex + requestBaseUri.length());
- rawServiceResolutionUri = "";
- rawRequestUri = requestBaseUri + rawODataPath;
-
- if (queryOptions != null) {
- rawRequestUri += "?" + queryOptions;
- rawQueryPath = queryOptions;
- } else {
- rawQueryPath = "";
- }
- }
-
- private String parseHttpVersion(final String httpVersion) throws BatchException {
- if (!HTTP_VERSION.equals(httpVersion.trim())) {
- throw new BatchException("Invalid http version", MessageKeys.INVALID_HTTP_VERSION, statusLine.getLineNumber());
- } else {
- return HTTP_VERSION;
- }
- }
-
- public void validateHttpMethod(boolean isChangeSet) throws BatchException {
- Set<String> validMethods = (isChangeSet) ? HTTP_CHANGE_SET_METHODS : HTTP_BATCH_METHODS;
-
- if (!validMethods.contains(getMethod().toString())) {
- if (isChangeSet) {
- throw new BatchException("Invalid change set method", MessageKeys.INVALID_CHANGESET_METHOD, statusLine
- .getLineNumber());
- } else {
- throw new BatchException("Invalid query operation method", MessageKeys.INVALID_QUERY_OPERATION_METHOD,
- statusLine.getLineNumber());
- }
- }
- }
-
- public HttpMethod getMethod() {
- return method;
- }
-
- public String getHttpVersion() {
- return httpVersion;
- }
-
- public int getLineNumber() {
- return statusLine.getLineNumber();
- }
-
- public String getRawBaseUri() {
- return rawBaseUri;
- }
-
- public String getRawODataPath() {
- return rawODataPath;
- }
-
- public String getRawQueryPath() {
- return rawQueryPath;
- }
-
- public String getRawRequestUri() {
- return rawRequestUri;
- }
-
- public String getRawServiceResolutionUri() {
- return rawServiceResolutionUri;
- }
- }
}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/HttpRequestStatusLine.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/HttpRequestStatusLine.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/HttpRequestStatusLine.java
new file mode 100644
index 0000000..416d593
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/HttpRequestStatusLine.java
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.transformator;
+
+import java.util.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.batch.parser.Header;
+import org.apache.olingo.server.core.batch.parser.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", "MERGE", "PATCH" }));
+ // TODO Is Merge still supported?
+ // What`s New in OData 4: 2.7.2 Pruned: MERGE
+ // MERGE was used to do PATCH before PATCH existed. Now that we have PATCH, we no longer need MERGE. => No
+ 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/0bd32951/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java
index 9196514..c8b725a 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java
@@ -39,7 +39,7 @@ public class BatchChangeSetSorterTest {
public void test() throws BatchException {
final List<ODataRequest> changeSet = new ArrayList<ODataRequest>();
changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$1/Adress", "2"));
- changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employees", "1"));
+ changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "/Employees", "1"));
BatchChangeSetSorter sorter = new BatchChangeSetSorter(changeSet);
final List<ODataRequest> sortedChangeSet = sorter.getOrderdRequests();
@@ -110,7 +110,7 @@ public class BatchChangeSetSorterTest {
@Test
public void testRewriting() {
final String CONTENT_ID = "1";
- final String ODATA_PATH ="$" + CONTENT_ID + "/Address";
+ final String ODATA_PATH ="/$" + CONTENT_ID + "/Address";
final String RESOURCE_URI = "Employee('1')";
final ODataRequest request = createRequest(HttpMethod.POST, BASE_URI, ODATA_PATH);
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
new file mode 100644
index 0000000..ac111e0
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
@@ -0,0 +1,582 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.handler;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.olingo.commons.api.ODataRuntimeException;
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.commons.api.http.HttpStatusCode;
+import org.apache.olingo.server.api.OData;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.ServiceMetadata;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchOperation;
+import org.apache.olingo.server.api.batch.BatchRequestPart;
+import org.apache.olingo.server.api.batch.ODataResponsePart;
+import org.apache.olingo.server.api.processor.BatchProcessor;
+import org.apache.olingo.server.core.ODataHandler;
+import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MockedBatchHandlerTest {
+
+ private static final String BATCH_CONTENT_TYPE = "multipart/mixed;boundary=batch_12345";
+ private static final String BATCH_ODATA_PATH = "/$batch";
+ private static final String BATCH_REQUEST_URI = "http://localhost:8080/odata/$batch";
+ private static final String BASE_URI = "http://localhost:8080/odata";
+ private static final String CRLF = "\r\n";
+ private ODataHandler handler;
+ private int entityCounter = 1;
+
+ @Test
+ public void test() throws BatchException, IOException {
+ final String content = "--batch_12345" + CRLF
+ + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
+ + CRLF
+ + "--changeset_12345" + CRLF
+ + "Content-Type: application/http" + CRLF
+ + "Content-Transfer-Encoding: binary" + CRLF
+ + "Content-Id: 4" + CRLF
+ + CRLF
+ + "PUT /$3/PropertyInt32 HTTP/1.1" + CRLF // Absolute URI with separate Host header and ref.
+ + "Host: http://localhost:8080/odata" + CRLF
+ + "Content-Type: application/json;odata=verbose" + CRLF
+ + CRLF
+ + CRLF
+ + "--changeset_12345" + CRLF
+ + "Content-Type: application/http" + CRLF
+ + "Content-Transfer-Encoding: binary" + CRLF
+ + "Content-Id: 5" + CRLF
+ + CRLF
+ + "POST http://localhost:8080/odata/$1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF // Absolute URI with ref.
+ + "Content-Type: application/json;odata=verbose" + CRLF
+ + CRLF
+ + CRLF
+ + "--changeset_12345" + CRLF
+ + "Content-Type: application/http" + CRLF
+ + "Content-Transfer-Encoding: binary" + CRLF
+ + "Content-Id: 2" + CRLF
+ + CRLF
+ + "POST $1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF // Relative URI with ref.
+ + "Content-Type: application/json;odata=verbose" + CRLF
+ + CRLF
+ + CRLF
+ + "--changeset_12345" + CRLF
+ + "Content-Type: application/http" + CRLF
+ + "Content-Transfer-Encoding: binary" + CRLF
+ + "Content-Id: 1" + CRLF
+ + CRLF
+ + "POST http://localhost:8080/odata/ESAllPrim HTTP/1.1" + CRLF // Absolute URI
+ + "Content-Type: application/json;odata=verbose" + CRLF
+ + CRLF
+ + CRLF
+ + "--changeset_12345" + CRLF
+ + "Content-Type: application/http" + CRLF
+ + "Content-Transfer-Encoding: binary" + CRLF
+ + "Content-Id: 3" + CRLF
+ + CRLF
+ + "PUT ESAllPrim(1) HTTP/1.1" + CRLF // Relative URI
+ + "Content-Type: application/json;odata=verbose" + CRLF
+ + CRLF
+ + CRLF
+ + "--changeset_12345" + CRLF
+ + "Content-Type: application/http" + CRLF
+ + "Content-Transfer-Encoding: binary" + CRLF
+ + "Content-Id: 6" + CRLF
+ + CRLF
+ + "PUT /ESAllPrim(1) HTTP/1.1" + CRLF // Absolute URI with separate Host header
+ + "Host: http://localhost:8080/odata"
+ + "Content-Type: application/json;odata=verbose" + CRLF
+ + CRLF
+ + CRLF
+ + "--changeset_12345--" + CRLF
+ + CRLF
+ + "--batch_12345--";
+ final Map<String, List<String>> header = getMimeHeader();
+ final ODataResponse response = new ODataResponse();
+ final BatchHandler batchHandler = buildBatchHandler(content, header);
+
+ batchHandler.process(response);
+
+ BufferedReaderIncludingLineEndings reader =
+ new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
+
+ final List<String> responseContent = reader.toList();
+ reader.close();
+
+ int line = 0;
+ assertEquals(63, responseContent.size());
+
+ // Check change set
+ assertTrue(responseContent.get(line++).contains("--batch_"));
+ assertTrue(responseContent.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
+
+ for (int i = 0; i < 6; i++) {
+ String contentId = checkChangeSetPartHeader(responseContent, line);
+ line += 6;
+
+ if ("1".equals(contentId)) {
+ assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+ assertEquals("Location: " + BASE_URI + "/ESAllPrim(1)" + CRLF, responseContent.get(line++));
+ assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+ } else if ("2".equals(contentId)) {
+ assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+ assertEquals("Location: " + BASE_URI + "/ESTwoPrim(3)" + CRLF, responseContent.get(line++));
+ assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+ } else if ("3".equals(contentId)) {
+ assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+ assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+ } else if ("4".equals(contentId)) {
+ assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+ assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+ } else if ("5".equals(contentId)) {
+ assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+ assertEquals("Location: " + BASE_URI + "/ESTwoPrim(2)" + CRLF, responseContent.get(line++));
+ assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+ } else if ("6".equals(contentId)) {
+ assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+ assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+ } else {
+ fail();
+ }
+
+ assertEquals(CRLF, responseContent.get(line++));
+ }
+
+ // Close body part (change set)
+ assertEquals(CRLF, responseContent.get(line++));
+ assertTrue(responseContent.get(line++).contains("--changeset_"));
+
+ // Close batch
+ assertEquals(CRLF, responseContent.get(line++));
+ assertTrue(responseContent.get(line++).contains("--batch_"));
+ assertEquals(63, line);
+ }
+
+ @Test
+ public void testMultipleChangeSets() throws BatchException, IOException {
+ final String content = ""
+ + "--batch_12345" + CRLF
+ + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
+ + CRLF
+ + "--changeset_12345" + CRLF
+ + "Content-Type: application/http" + CRLF
+ + "Content-Transfer-Encoding: binary" + CRLF
+ + "Content-Id: 2" + CRLF
+ + CRLF
+ + "POST /$1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF
+ + "Host: http://localhost:8080/odata" + CRLF
+ + "Content-Type: application/json;odata=verbose" + CRLF
+ + CRLF
+ + CRLF
+ + "--changeset_12345" + CRLF
+ + "Content-Type: application/http" + CRLF
+ + "Content-Transfer-Encoding: binary" + CRLF
+ + "Content-Id: 1" + CRLF
+ + CRLF
+ + "PUT ESAllPrim(1) HTTP/1.1" + CRLF
+ + "Content-Type: application/json;odata=verbose" + CRLF
+ + CRLF
+ + CRLF
+ + "--changeset_12345--" + CRLF
+
+ + "--batch_12345" + CRLF
+ + "Content-Type: multipart/mixed; boundary=changeset_54321" + CRLF
+ + CRLF
+ + "--changeset_54321" + CRLF
+ + "Content-Type: application/http" + CRLF
+ + "Content-Transfer-Encoding: binary" + CRLF
+ + "Content-Id: 2" + CRLF
+ + CRLF
+ + "POST /$1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF
+ + "Host: http://localhost:8080/odata" + CRLF
+ + "Content-Type: application/json;odata=verbose" + CRLF
+ + CRLF
+ + CRLF
+ + "--changeset_54321" + CRLF
+ + "Content-Type: application/http" + CRLF
+ + "Content-Transfer-Encoding: binary" + CRLF
+ + "Content-Id: 1" + CRLF
+ + CRLF
+ + "PUT ESAllPrim(2) HTTP/1.1" + CRLF
+ + "Content-Type: application/json;odata=verbose" + CRLF
+ + CRLF
+ + CRLF
+ + "--changeset_54321--" + CRLF
+
+ + CRLF
+ + "--batch_12345--";
+ final Map<String, List<String>> header = getMimeHeader();
+ final ODataResponse response = new ODataResponse();
+ final BatchHandler batchHandler = buildBatchHandler(content, header);
+
+ batchHandler.process(response);
+
+ BufferedReaderIncludingLineEndings reader =
+ new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
+
+ final List<String> responseContent = reader.toList();
+ reader.close();
+
+ int line = 0;
+ assertEquals(49, responseContent.size());
+
+ // Check first change set
+ assertTrue(responseContent.get(line++).contains("--batch_"));
+ assertTrue(responseContent.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
+
+ for (int i = 0; i < 2; i++) {
+ String contentId = checkChangeSetPartHeader(responseContent, line);
+ line += 6;
+
+ if ("1".equals(contentId)) {
+ assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+ assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+ } else if ("2".equals(contentId)) {
+ assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+ assertEquals("Location: " + BASE_URI + "/ESTwoPrim(1)" + CRLF, responseContent.get(line++));
+ assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+ } else {
+ fail();
+ }
+
+ assertEquals(CRLF, responseContent.get(line++));
+ }
+ // Close body part (1st change set)
+ assertEquals(CRLF, responseContent.get(line++));
+ assertTrue(responseContent.get(line++).contains("--changeset_"));
+
+ // Check second change set
+ assertEquals(CRLF, responseContent.get(line++));
+ assertTrue(responseContent.get(line++).contains("--batch_"));
+ assertTrue(responseContent.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
+
+ for (int i = 0; i < 2; i++) {
+ String contentId = checkChangeSetPartHeader(responseContent, line);
+ line += 6;
+
+ if ("1".equals(contentId)) {
+ assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+ assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+ } else if ("2".equals(contentId)) {
+ assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+ assertEquals("Location: " + BASE_URI + "/ESTwoPrim(2)" + CRLF, responseContent.get(line++));
+ assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+ } else {
+ fail();
+ }
+
+ assertEquals(CRLF, responseContent.get(line++));
+ }
+ // Close body part (2nd change set)
+ assertEquals(CRLF, responseContent.get(line++));
+ assertTrue(responseContent.get(line++).contains("--changeset_"));
+
+ // Close batch
+ assertEquals(CRLF, responseContent.get(line++));
+ assertTrue(responseContent.get(line++).contains("--batch_"));
+
+ assertEquals(49, line);
+ }
+
+ @Test
+ public void testMineBodyPartTransitiv() throws BatchException, IOException {
+ final String content = ""
+ + "--batch_12345" + CRLF
+ + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
+ + CRLF
+ + "--changeset_12345" + CRLF
+ + "Content-Type: application/http" + CRLF
+ + "Content-Transfer-Encoding: binary" + CRLF
+ + "Content-Id: 4" + CRLF
+ + CRLF
+ + "POST $3/NavPropertyETTwoPrimOne HTTP/1.1" + CRLF
+ + "Content-Type: application/json;odata=verbose" + CRLF
+ + CRLF
+ + CRLF
+ + "--changeset_12345" + CRLF
+ + "Content-Type: application/http" + CRLF
+ + "Content-Transfer-Encoding: binary" + CRLF
+ + "Content-Id: 2" + CRLF
+ + CRLF
+ + "POST /$1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF
+ + "Host: http://localhost:8080/odata" + CRLF
+ + "Content-Type: application/json;odata=verbose" + CRLF
+ + CRLF
+ + CRLF
+ + "--changeset_12345" + CRLF
+ + "Content-Type: application/http" + CRLF
+ + "Content-Transfer-Encoding: binary" + CRLF
+ + "Content-Id: 1" + CRLF
+ + CRLF
+ + "PUT ESAllPrim(1) HTTP/1.1" + CRLF
+ + "Content-Type: application/json;odata=verbose" + CRLF
+ + CRLF
+ + CRLF
+ + "--changeset_12345" + CRLF
+ + "Content-Type: application/http" + CRLF
+ + "Content-Transfer-Encoding: binary" + CRLF
+ + "Content-Id: 3" + CRLF
+ + CRLF
+ + "POST $2/NavPropertyETAllPrimMany HTTP/1.1" + CRLF
+ + "Content-Type: application/json;odata=verbose" + CRLF
+ + CRLF
+ + CRLF
+ + "--changeset_12345--" + CRLF
+
+ + CRLF
+ + "--batch_12345--";
+
+ final Map<String, List<String>> header = getMimeHeader();
+ final ODataResponse response = new ODataResponse();
+ final BatchHandler batchHandler = buildBatchHandler(content, header);
+
+ batchHandler.process(response);
+
+ BufferedReaderIncludingLineEndings reader =
+ new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
+
+ final List<String> responseContent = reader.toList();
+ reader.close();
+
+ int line = 0;
+ assertEquals(45, responseContent.size());
+
+ // Check change set
+ assertTrue(responseContent.get(line++).contains("--batch_"));
+ assertTrue(responseContent.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
+
+ for (int i = 0; i < 4; i++) {
+ String contentId = checkChangeSetPartHeader(responseContent, line);
+ line += 6;
+
+ if ("1".equals(contentId)) {
+ assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+ assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+ } else if ("2".equals(contentId)) {
+ assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+ assertEquals("Location: " + BASE_URI + "/ESTwoPrim(1)" + CRLF, responseContent.get(line++));
+ assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+ } else if ("3".equals(contentId)) {
+ assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+ assertEquals("Location: " + BASE_URI + "/ESAllPrim(2)" + CRLF, responseContent.get(line++));
+ assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+ } else if ("4".equals(contentId)) {
+ assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+ assertEquals("Location: " + BASE_URI + "/ESTwoPrim(3)" + CRLF, responseContent.get(line++));
+ assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+ } else {
+ fail();
+ }
+
+ assertEquals(CRLF, responseContent.get(line++));
+ }
+
+ // Close body part (change set)
+ assertEquals(CRLF, responseContent.get(line++));
+ assertTrue(responseContent.get(line++).contains("--changeset_"));
+
+ // Close batch
+ assertEquals(CRLF, responseContent.get(line++));
+ assertTrue(responseContent.get(line++).contains("--batch_"));
+ assertEquals(45, line);
+ }
+
+ @Before
+ public void setup() {
+ handler = null;
+ entityCounter = 1;
+ }
+
+ private String checkChangeSetPartHeader(final List<String> response, int line) {
+ assertEquals(CRLF, response.get(line++));
+ assertTrue(response.get(line++).contains("--changeset_"));
+ assertEquals("Content-Type: application/http" + CRLF, response.get(line++));
+ assertEquals("Content-Transfer-Encoding: binary" + CRLF, response.get(line++));
+
+ assertTrue(response.get(line).contains("Content-Id:"));
+ String contentId = response.get(line).split(":")[1].trim();
+ line++;
+
+ assertEquals(CRLF, response.get(line++));
+
+ return contentId;
+ }
+
+ /*
+ * Helper methods
+ */
+
+ private Map<String, List<String>> getMimeHeader() {
+ final Map<String, List<String>> header = new HashMap<String, List<String>>();
+ header.put(HttpHeader.CONTENT_TYPE, Arrays.asList(new String[] { BATCH_CONTENT_TYPE }));
+
+ return header;
+ }
+
+ private BatchHandler buildBatchHandler(final String content, Map<String, List<String>> header) throws BatchException,
+ UnsupportedEncodingException {
+
+ final ODataRequest request = buildODataRequest(content, header);
+ final ODataHandler oDataHandler = buildODataHandler(request);
+ final BatchProcessor batchProcessor = new BatchProcessorImpl();
+
+ return new BatchHandler(oDataHandler, request, batchProcessor, true);
+ }
+
+ private ODataHandler buildODataHandler(ODataRequest request) {
+ handler = mock(ODataHandler.class);
+ when(handler.process(request)).thenCallRealMethod();
+
+ return handler;
+ }
+
+ private ODataRequest buildODataRequest(String content, Map<String, List<String>> header)
+ throws UnsupportedEncodingException {
+ final ODataRequest request = new ODataRequest();
+
+ for (final String key : header.keySet()) {
+ request.addHeader(key, header.get(key));
+ }
+
+ request.setMethod(HttpMethod.POST);
+ request.setRawBaseUri(BASE_URI);
+ request.setRawODataPath(BATCH_ODATA_PATH);
+ request.setRawQueryPath("");
+ request.setRawRequestUri(BATCH_REQUEST_URI);
+ request.setRawServiceResolutionUri("");
+
+ request.setBody(new ByteArrayInputStream(content.getBytes("UTF-8")));
+
+ return request;
+ }
+
+ /**
+ * Batch processor
+ */
+ private class BatchProcessorImpl implements BatchProcessor {
+ @Override
+ public void init(OData odata, ServiceMetadata serviceMetadata) {}
+
+ @Override
+ public List<ODataResponse> executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
+ BatchRequestPart requestPart) {
+ List<ODataResponse> responses = new ArrayList<ODataResponse>();
+
+ for (ODataRequest request : requests) {
+ // Mock the processor of the changeset requests
+ when(handler.process(request)).then(new Answer<ODataResponse>() {
+ @Override
+ public ODataResponse answer(InvocationOnMock invocation) throws Throwable {
+ Object[] arguments = invocation.getArguments();
+
+ return buildResponse((ODataRequest) arguments[0]);
+ }
+ });
+
+ try {
+ responses.add(operation.handleODataRequest(request, requestPart));
+ } catch (BatchException e) {
+ fail();
+ }
+ }
+
+ return responses;
+ }
+
+ @Override
+ public void executeBatch(BatchOperation operation, ODataRequest request, ODataResponse response) {
+ try {
+ final List<BatchRequestPart> parts = operation.parseBatchRequest(request.getBody());
+ final List<ODataResponsePart> responseParts = new ArrayList<ODataResponsePart>();
+
+ for (BatchRequestPart part : parts) {
+ responseParts.add(operation.handleBatchRequest(part));
+ }
+
+ operation.writeResponseParts(responseParts, response);
+ } catch (BatchException e) {
+ throw new ODataRuntimeException(e);
+ } catch (IOException e) {
+ throw new ODataRuntimeException(e);
+ }
+ }
+ }
+
+ private ODataResponse buildResponse(ODataRequest request) {
+ final ODataResponse oDataResponse = new ODataResponse();
+
+ if (request.getMethod() == HttpMethod.POST) {
+ oDataResponse.setStatusCode(HttpStatusCode.CREATED.getStatusCode());
+ oDataResponse.setHeader(HttpHeader.LOCATION, createResourceUri(request));
+ } else {
+ oDataResponse.setStatusCode(HttpStatusCode.OK.getStatusCode());
+ }
+
+ final String contentId = request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
+ if (contentId != null) {
+ oDataResponse.setHeader(BatchParserCommon.HTTP_CONTENT_ID, contentId);
+ }
+
+ return oDataResponse;
+ }
+
+ private String createResourceUri(final ODataRequest request) {
+ final String parts[] = request.getRawODataPath().split("/");
+ String oDataPath = "";
+
+ if (parts.length == 2) {
+ // Entity Collection
+ oDataPath = parts[1];
+ } else {
+ // Navigationproperty
+
+ final String navProperty = parts[parts.length - 1];
+ if (navProperty.equals("NavPropertyETTwoPrimMany")) {
+ oDataPath = "ESTwoPrim";
+ } else if (navProperty.equals("NavPropertyETAllPrimMany")) {
+ oDataPath = "ESAllPrim";
+ } else if (navProperty.equals("NavPropertyETTwoPrimOne")) {
+ oDataPath = "ESTwoPrim";
+ }
+ }
+
+ return BASE_URI + "/" + oDataPath + "(" + entityCounter++ + ")";
+ }
+}