Package org.apache.olingo.odata2.core.batch

Source Code of org.apache.olingo.odata2.core.batch.BatchResponseParser

/*******************************************************************************
* 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 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_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(LF);
    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 contentLengthHeader = getHeaderValue(headers, HttpHeaders.CONTENT_LENGTH);
      String body =
          (contentLengthHeader != null) ? parseBody(scanner, Integer.parseInt(contentLengthHeader))
              : parseBody(scanner);
      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 (!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++;
    }
    String responseBody = body != null ? body.toString() : null;
    return responseBody;
  }

  private String parseBody(final Scanner scanner, final int contentLength) {
    StringBuilder body = null;
    int length = 0;
    while (scanner.hasNext() && length < contentLength) {
      if (!scanner.hasNext(REG_EX_ZERO_OR_MORE_WHITESPACES)) {
        String nextLine = scanner.next();
        length += BatchHelper.getBytes(nextLine).length;
        if (body == null) {
          body = new StringBuilder(nextLine);
        } else {
          body.append(LF).append(nextLine);
        }
      } else {
        scanner.next();
      }
      currentLineNumber++;
      if (scanner.hasNext() && scanner.hasNext(REG_EX_BLANK_LINE)) {
        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;
  }

}
TOP

Related Classes of org.apache.olingo.odata2.core.batch.BatchResponseParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.