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