/******************************************************************************* * Copyright (c) 2011 Subgraph. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Subgraph - initial API and implementation ******************************************************************************/ package com.subgraph.vega.internal.http.requests; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.apache.http.HttpEntity; import org.apache.http.entity.AbstractHttpEntity; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.util.ByteArrayBuffer; import org.apache.http.util.EntityUtils; /** * An entity wrapper which converts a non-repeatable streaming entity * into a repeatable entity by storing the data as it streams in from * a connection (or other streaming source). When all streaming data has * been received, the entity behaves like a ByteArrayEntity. */ public class RepeatableStreamingEntity extends AbstractHttpEntity { private final static int BUFFER_SIZE = 8192; private boolean isStreaming = true; private long length; private long maximumInputKilobytes = 0; // 0 = no maximum length private HttpEntity bufferEntity; private volatile InputStream input; /** * @param consumeInput Boolean indicating whether to immediately consume all input into buffer. * @throws IOException */ RepeatableStreamingEntity(InputStream input, long length, boolean consumeInput, boolean isChunked, String contentType, String contentEncoding) throws IOException { setChunked(isChunked); setContentType(contentType); setContentEncoding(contentEncoding); setActiveInputStream(input, length); if (consumeInput != false) { consumeAllInput(); } } RepeatableStreamingEntity(HttpEntity originalEntity) throws IOException { copyEntityProperties(originalEntity); if(originalEntity.isStreaming()) setActiveInputStream(originalEntity.getContent(), originalEntity.getContentLength()); else setActiveByteArrayEntity(EntityUtils.toByteArray(originalEntity)); } void setMaximumInputKilobytes(int kb) { maximumInputKilobytes = kb; } private void copyEntityProperties(HttpEntity e) { setChunked(e.isChunked()); if(e.getContentType() != null) setContentType(e.getContentType().getValue()); if(e.getContentEncoding() != null) setContentEncoding(e.getContentType().getValue()); length = e.getContentLength(); } private void setActiveInputStream(InputStream input, long length) { if(length > Integer.MAX_VALUE) throw new IllegalArgumentException("HTTP entity is too large to be buffered in memory: "+ length); this.length = length; final int sz = (length < 0) ? (BUFFER_SIZE) : ((int) length); this.input = new CachingInputStream(input, sz); isStreaming = true; } private void setActiveByteArrayEntity(byte[] content) { ByteArrayEntity entity = new ByteArrayEntity(content); isStreaming = false; length = content.length; bufferEntity = entity; input = null; } private void consumeAllInput() throws IOException { int rv; if (length < 0) { while ((rv = input.read()) != -1); } else { int remaining = (int)this.length; byte[] buffer = new byte[BUFFER_SIZE]; while (remaining > 0) { int sz = (remaining < BUFFER_SIZE) ? (remaining) : (BUFFER_SIZE); rv = input.read(buffer, 0, sz); if(rv == -1) break; remaining -= rv; } } } @Override public boolean isRepeatable() { return true; } @Override public long getContentLength() { return length; } @Override public InputStream getContent() throws IOException { if(input != null) return input; else return bufferEntity.getContent(); } @Override public void writeTo(OutputStream outstream) throws IOException { if(input == null) { bufferEntity.writeTo(outstream); return; } byte[] buffer = new byte[BUFFER_SIZE]; int l; if(length < 0) { // consume until EOF while((l = input.read(buffer)) != -1) { try { outstream.write(buffer, 0, l); } catch(Exception e) { e.printStackTrace(); } } } else { // consume no more than length long remaining = this.length; while(remaining > 0) { int sz = (remaining < BUFFER_SIZE) ? ((int) remaining) : (BUFFER_SIZE); l = input.read(buffer, 0, sz); if(l == -1) break; outstream.write(buffer, 0, l); remaining -= l; } } if(input != null) { input.close(); input = null; } } @Override public boolean isStreaming() { return isStreaming; } @Override public void consumeContent() throws IOException { if(input != null) { input.close(); input = null; } if(bufferEntity != null) { EntityUtils.consume(bufferEntity); } } private class CachingInputStream extends InputStream { private final InputStream wrappedInput; private ByteArrayBuffer buffer; private boolean eof; CachingInputStream(InputStream input, int bufferSize) { wrappedInput = input; buffer = new ByteArrayBuffer(bufferSize); } @Override public int read() throws IOException { if(eof) { return -1; } final int b = wrappedInput.read(); if(b == -1) { processEOF(); } else { buffer.append(b); } checkMaximumLength(); return b; } public int read(byte[] b, int off, int len) throws IOException { if(eof) { return -1; } final int n = wrappedInput.read(b, off, len); if(n == -1) { processEOF(); } else { buffer.append(b, off, n); } checkMaximumLength(); return n; } private void checkMaximumLength() throws IOException { if(maximumInputKilobytes > 0 && buffer.length() > (maximumInputKilobytes * 1024)) { wrappedInput.close(); eof = true; buffer = null; setActiveByteArrayEntity(new byte[0]); throw new IOException("Maximum length of "+ maximumInputKilobytes +" kb exceeded while streaming http entity."); } } public void close() throws IOException { wrappedInput.close(); if(!eof) { setActiveByteArrayEntity(buffer.toByteArray()); } } private void processEOF() throws IOException { wrappedInput.close(); eof = true; setActiveByteArrayEntity(buffer.toByteArray()); } } }