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