All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.api.client.googleapis.batch.BatchUnparsedResponse Maven / Gradle / Ivy

/*
 * Copyright 2012 Google Inc.
 *
 * Licensed 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 com.google.api.client.googleapis.batch;

import com.google.api.client.googleapis.batch.BatchRequest.RequestInfo;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpContent;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpStatusCodes;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.HttpUnsuccessfulResponseHandler;
import com.google.api.client.http.LowLevelHttpRequest;
import com.google.api.client.http.LowLevelHttpResponse;
import com.google.api.client.util.ByteStreams;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * The unparsed batch response.
 *
 * @author [email protected] (Ravi Mistry)
 */
final class BatchUnparsedResponse {

  /** The boundary used in the batch response to separate individual responses. */
  private final String boundary;

  /** List of request infos. */
  private final List> requestInfos;

  /** Input stream that contains the batch response. */
  private final InputStream inputStream;

  /** Determines whether there are any responses to be parsed. */
  boolean hasNext = true;

  /** List of unsuccessful HTTP requests that can be retried. */
  List> unsuccessfulRequestInfos = new ArrayList>();

  /** The content Id the response is currently at. */
  private int contentId = 0;

  /** Whether unsuccessful HTTP requests can be retried. */
  private final boolean retryAllowed;

  /**
   * Construct the {@link BatchUnparsedResponse}.
   *
   * @param inputStream Input stream that contains the batch response
   * @param boundary The boundary of the batch response
   * @param requestInfos List of request infos
   * @param retryAllowed Whether unsuccessful HTTP requests can be retried
   */
  BatchUnparsedResponse(
      InputStream inputStream,
      String boundary,
      List> requestInfos,
      boolean retryAllowed)
      throws IOException {
    this.boundary = boundary;
    this.requestInfos = requestInfos;
    this.retryAllowed = retryAllowed;
    this.inputStream = inputStream;
    // First line in the stream will be the boundary.
    checkForFinalBoundary(readLine());
  }

  /**
   * Parses the next response in the queue if a data class and a {@link BatchCallback} is specified.
   *
   * 

This method closes the input stream if there are no more individual responses left. */ void parseNextResponse() throws IOException { contentId++; // Extract the outer headers. String line; while ((line = readLine()) != null && !line.equals("")) { // Do nothing. } // Extract the status code. String statusLine = readLine(); String[] statusParts = statusLine.split(" "); int statusCode = Integer.parseInt(statusParts[1]); // Extract and store the inner headers. // TODO(rmistry): Handle inner headers that span multiple lines. More details here: // http://tools.ietf.org/html/rfc2616#section-2.2 List headerNames = new ArrayList(); List headerValues = new ArrayList(); long contentLength = -1L; while ((line = readLine()) != null && !line.equals("")) { String[] headerParts = line.split(": ", 2); String headerName = headerParts[0]; String headerValue = headerParts[1]; headerNames.add(headerName); headerValues.add(headerValue); if ("Content-Length".equalsIgnoreCase(headerName.trim())) { contentLength = Long.parseLong(headerValue); } } InputStream body; if (contentLength == -1) { // This isn't very efficient, but most respectable servers should set the Content-Length ByteArrayOutputStream buffer = new ByteArrayOutputStream(); while ((line = readRawLine()) != null && !line.startsWith(boundary)) { // Convert characters back to bytes: buffer.write(line.getBytes("ISO-8859-1")); } // Remove CRLF that separates body from boundary token body = trimCrlf(buffer.toByteArray()); // Remove CRLF from the boundary token (to match readLine) line = trimCrlf(line); } else { body = new FilterInputStream(ByteStreams.limit(inputStream, contentLength)) { @Override public void close() { // Don't allow the parser to close the underlying stream } }; } HttpResponse response = getFakeResponse(statusCode, body, headerNames, headerValues); parseAndCallback(requestInfos.get(contentId - 1), statusCode, response); // Consume any bytes that were not consumed by the parser while (body.skip(contentLength) > 0 || body.read() != -1) {} if (contentLength != -1) { line = readLine(); } else { // The line was already read } // Consume any blank lines that follow the response (not included in Content-Length) while ((line != null) && (line.length() == 0)) { line = readLine(); } checkForFinalBoundary(line); } /** * Parse an object into a new instance of the data class using {@link * HttpResponse#parseAs(java.lang.reflect.Type)}. */ private void parseAndCallback( RequestInfo requestInfo, int statusCode, HttpResponse response) throws IOException { BatchCallback callback = requestInfo.callback; HttpHeaders responseHeaders = response.getHeaders(); HttpUnsuccessfulResponseHandler unsuccessfulResponseHandler = requestInfo.request.getUnsuccessfulResponseHandler(); if (HttpStatusCodes.isSuccess(statusCode)) { if (callback == null) { // No point in parsing if there is no callback. return; } T parsed = getParsedDataClass(requestInfo.dataClass, response, requestInfo); callback.onSuccess(parsed, responseHeaders); } else { HttpContent content = requestInfo.request.getContent(); boolean retrySupported = retryAllowed && (content == null || content.retrySupported()); boolean errorHandled = false; boolean redirectRequest = false; if (unsuccessfulResponseHandler != null) { errorHandled = unsuccessfulResponseHandler.handleResponse( requestInfo.request, response, retrySupported); } if (!errorHandled) { if (requestInfo.request.handleRedirect(response.getStatusCode(), response.getHeaders())) { redirectRequest = true; } } if (retrySupported && (errorHandled || redirectRequest)) { unsuccessfulRequestInfos.add(requestInfo); } else { if (callback == null) { // No point in parsing if there is no callback. return; } E parsed = getParsedDataClass(requestInfo.errorClass, response, requestInfo); callback.onFailure(parsed, responseHeaders); } } } private A getParsedDataClass( Class dataClass, HttpResponse response, RequestInfo requestInfo) throws IOException { // TODO(yanivi): Remove the HttpResponse reference and directly parse the InputStream if (dataClass == Void.class) { return null; } return requestInfo .request .getParser() .parseAndClose(response.getContent(), response.getContentCharset(), dataClass); } /** Create a fake HTTP response object populated with the partContent and the statusCode. */ private HttpResponse getFakeResponse( final int statusCode, final InputStream partContent, List headerNames, List headerValues) throws IOException { HttpRequest request = new FakeResponseHttpTransport(statusCode, partContent, headerNames, headerValues) .createRequestFactory() .buildPostRequest(new GenericUrl("http://google.com/"), null); request.setLoggingEnabled(false); request.setThrowExceptionOnExecuteError(false); return request.execute(); } /** * Reads an HTTP response line (ISO-8859-1 encoding). * * @return The line that was read, including CRLF. */ private String readRawLine() throws IOException { int b = inputStream.read(); if (b == -1) { return null; } else { StringBuilder buffer = new StringBuilder(); for (; b != -1; b = inputStream.read()) { buffer.append((char) b); if (b == '\n') { break; } } return buffer.toString(); } } /** * Reads an HTTP response line (ISO-8859-1 encoding) * *

This method is similar to {@link java.io.BufferedReader#readLine()}, but handles newlines in * a way that is consistent with the HTTP RFC 2616. * * @return The line that was read, excluding CRLF. */ private String readLine() throws IOException { return trimCrlf(readRawLine()); } private static String trimCrlf(String input) { if (input.endsWith("\r\n")) { return input.substring(0, input.length() - 2); } else if (input.endsWith("\n")) { return input.substring(0, input.length() - 1); } else { return input; } } private static InputStream trimCrlf(byte[] bytes) { int length = bytes.length; if (length > 0 && bytes[length - 1] == '\n') { length--; } if (length > 0 && bytes[length - 1] == '\r') { length--; } return new ByteArrayInputStream(bytes, 0, length); } /** * If the boundary line consists of the boundary and "--" then there are no more individual * responses left to be parsed and the input stream is closed. */ private void checkForFinalBoundary(String boundaryLine) throws IOException { if (boundaryLine.equals(boundary + "--")) { hasNext = false; inputStream.close(); } } private static class FakeResponseHttpTransport extends HttpTransport { private int statusCode; private InputStream partContent; private List headerNames; private List headerValues; FakeResponseHttpTransport( int statusCode, InputStream partContent, List headerNames, List headerValues) { super(); this.statusCode = statusCode; this.partContent = partContent; this.headerNames = headerNames; this.headerValues = headerValues; } @Override protected LowLevelHttpRequest buildRequest(String method, String url) { return new FakeLowLevelHttpRequest(partContent, statusCode, headerNames, headerValues); } } private static class FakeLowLevelHttpRequest extends LowLevelHttpRequest { private InputStream partContent; private int statusCode; private List headerNames; private List headerValues; FakeLowLevelHttpRequest( InputStream partContent, int statusCode, List headerNames, List headerValues) { this.partContent = partContent; this.statusCode = statusCode; this.headerNames = headerNames; this.headerValues = headerValues; } @Override public void addHeader(String name, String value) {} @Override public LowLevelHttpResponse execute() { FakeLowLevelHttpResponse response = new FakeLowLevelHttpResponse(partContent, statusCode, headerNames, headerValues); return response; } } private static class FakeLowLevelHttpResponse extends LowLevelHttpResponse { private InputStream partContent; private int statusCode; private List headerNames = new ArrayList(); private List headerValues = new ArrayList(); FakeLowLevelHttpResponse( InputStream partContent, int statusCode, List headerNames, List headerValues) { this.partContent = partContent; this.statusCode = statusCode; this.headerNames = headerNames; this.headerValues = headerValues; } @Override public InputStream getContent() { return partContent; } @Override public int getStatusCode() { return statusCode; } @Override public String getContentEncoding() { return null; } @Override public long getContentLength() { return 0; } @Override public String getContentType() { return null; } @Override public String getStatusLine() { return null; } @Override public String getReasonPhrase() { return null; } @Override public int getHeaderCount() { return headerNames.size(); } @Override public String getHeaderName(int index) { return headerNames.get(index); } @Override public String getHeaderValue(int index) { return headerValues.get(index); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy