Index: trunk/http-core/src/contrib/org/apache/http/contrib/compress/GzipCompressingEntity.java =================================================================== --- trunk/http-core/src/contrib/org/apache/http/contrib/compress/GzipCompressingEntity.java (revision 374856) +++ trunk/http-core/src/contrib/org/apache/http/contrib/compress/GzipCompressingEntity.java (working copy) @@ -36,35 +36,28 @@ import org.apache.http.Header; import org.apache.http.HttpEntity; +import org.apache.http.entity.HttpEntityWrapper; import org.apache.http.protocol.HTTP; /** - *

- *

+ * Wrapping entity that compresses content when {@link #writeTo writing}. + * * @author Oleg Kalnichevski * * @version $Revision$ * * @since 4.0 */ -public class GzipCompressingEntity implements HttpEntity { +public class GzipCompressingEntity extends HttpEntityWrapper + implements HttpEntity { private static final String GZIP_CODEC = "gzip"; + - private final HttpEntity entity; - public GzipCompressingEntity(final HttpEntity entity) { - super(); - if (entity == null) { - throw new IllegalArgumentException("HTTP entity may not be null"); - } - this.entity = entity; + super(entity); } - public InputStream getContent() throws IOException { - return this.entity.getContent(); - } - public Header getContentEncoding() { return new Header(HTTP.CONTENT_ENCODING, GZIP_CODEC, true); } @@ -73,25 +66,17 @@ return -1; } - public Header getContentType() { - return this.entity.getContentType(); - } - public boolean isChunked() { // force content chunking return true; } - public boolean isRepeatable() { - return this.entity.isRepeatable(); - } - public boolean writeTo(final OutputStream outstream) throws IOException { if (outstream == null) { throw new IllegalArgumentException("Output stream may not be null"); } GZIPOutputStream gzip = new GZIPOutputStream(outstream); - InputStream in = this.entity.getContent(); + InputStream in = wrappedEntity.getContent(); byte[] tmp = new byte[2048]; int l; while ((l = in.read(tmp)) != -1) { @@ -102,4 +87,4 @@ return true; } -} +} // class GzipCompressingEntity Index: trunk/http-core/src/contrib/org/apache/http/contrib/compress/GzipDecompressingEntity.java =================================================================== --- trunk/http-core/src/contrib/org/apache/http/contrib/compress/GzipDecompressingEntity.java (revision 374856) +++ trunk/http-core/src/contrib/org/apache/http/contrib/compress/GzipDecompressingEntity.java (working copy) @@ -36,58 +36,40 @@ import org.apache.http.Header; import org.apache.http.HttpEntity; +import org.apache.http.entity.HttpEntityWrapper; /** - *

- *

+ * Wrapping entity that decompresses {@link #getContent content}. + * * @author Oleg Kalnichevski * * @version $Revision$ * * @since 4.0 */ -public class GzipDecompressingEntity implements HttpEntity { - - private final HttpEntity entity; +public class GzipDecompressingEntity extends HttpEntityWrapper + implements HttpEntity { + private InputStream instream = null; public GzipDecompressingEntity(final HttpEntity entity) { - super(); - if (entity == null) { - throw new IllegalArgumentException("HTTP entity may not be null"); - } - this.entity = entity; + super(entity); } public InputStream getContent() throws IOException { if (this.instream == null) { - this.instream = new GZIPInputStream(this.entity.getContent()); + this.instream = new GZIPInputStream(wrappedEntity.getContent()); } return this.instream; } - public Header getContentEncoding() { - return this.entity.getContentEncoding(); - } - public long getContentLength() { return -1; } - public Header getContentType() { - return this.entity.getContentType(); - } - - public boolean isChunked() { - return this.entity.isChunked(); - } - public boolean isRepeatable() { - return this.entity.isRepeatable(); + // not repeatable, GZIPInputStream is created only once + return false; } - public boolean writeTo(final OutputStream outstream) throws IOException { - return this.entity.writeTo(outstream); - } - -} +} // class GzipDecompressingEntity Index: trunk/http-core/src/java/org/apache/http/HttpEntity.java =================================================================== --- trunk/http-core/src/java/org/apache/http/HttpEntity.java (revision 374856) +++ trunk/http-core/src/java/org/apache/http/HttpEntity.java (working copy) @@ -34,8 +34,34 @@ import java.io.OutputStream; /** + * An entity that can be sent or received with an HTTP message. + * Entities can be found in some + * {@link HttpEntityEnclosingRequest requests} and in + * {@link HttpResponse responses}, where they are optional. *

- *

+ * In some places, the JavaDoc distinguishes three kinds of entities, + * depending on where their {@link #getContent content} originates: + * + * This distinction is important for connection management with incoming + * entities. For entities that are created by an application and only sent + * using the HTTP components framework, the difference between streamed + * and self-contained is of little importance. In that case, it is suggested + * to consider non-repeatable entities as streamed, and those that are + * repeatable (without a huge effort) as self-contained. + * * @author Oleg Kalnichevski * * @version $Revision$ @@ -52,27 +78,74 @@ */ boolean isRepeatable(); + + /** + * Tells about chunked encoding for this entity. + * The primary purpose of this method is to indicate whether + * chunked encoding should be used when the entity is sent. + * For entities that are received, it can also indicate whether + * the entity was received with chunked encoding. + *
+ * The behavior of wrapping entities is implementation dependent, + * but should respect the primary purpose. + * + * @return true if chunked encoding is preferred for this + * entity, or false if it is not + */ boolean isChunked(); + + /** + * Tells the length of the content, if known. + * + * @return the number of bytes of the content, or + * a negative number if unknown. If the content length is known + * but exceeds {@link java.lang.Long#MAX_VALUE Long.MAX_VALUE}, + * a negative number is returned. + */ long getContentLength(); - + + + /** + * Obtains the Content-Type header, if known. + * This is the header that should be used when sending the entity, + * or the one that was received with the entity. It can include a + * charset attribute. + * + * @return the Content-Type header for this entity, or + * null if the content type is unknown + */ Header getContentType(); - + + + /** + * Obtains the Content-Encoding header, if known. + * This is the header that should be used when sending the entity, + * or the one that was received with the entity. + * Wrapping entities that modify the content encoding should + * adjust this header accordingly. + * + * @return the Content-Encoding header for this entity, or + * null if the content encoding is unknown + */ Header getContentEncoding(); - + + /** - * Creates a new InputStream object of the entity. It is a programming error + * Creates a new InputStream object of the entity. + * It is a programming error * to return the same InputStream object more than once. * @return a new input stream that returns the entity data. * @throws IOException if the stream could not be created */ InputStream getContent() throws IOException; + /** * Writes the entity content to the output stream either partially or entirely. * This method may either write the entire content in one go, if it is feasible * to do so, or store the output stream as a local variable and use it internally - * to write the content in parts. If the former case this method MUST return + * to write the content in parts. In the former case this method MUST return * true to indicate that the output stream can be closed. In the latter * case the output stream MUST be closed once the last content part is written * in order to ensure that content codings that emit a closing chunk are properly @@ -84,5 +157,46 @@ * @throws IOException if an I/O error occurs */ boolean writeTo(OutputStream outstream) throws IOException; - -} + + + /** + * Tells whether this entity depends on an underlying stream. + * Streamed entities should return true until the + * content has been consumed, false afterwards. + * Self-contained entities should return false. + * Wrapping entities should delegate this call to the wrapped entity. + *
+ * The content of a streamed entity is consumed when the stream + * returned by {@link #getContent getContent} has been read to EOF, + * or after {@link #consumeContent consumeContent} has been called. + * If a streamed entity can not detect whether the stream has been + * read to EOF, it should return true until + * {@link #consumeContent consumeContent} is called. + * + * @return true if the entity content is streamed and + * not yet consumed, false otherwise + */ + public boolean isStreaming(); // don't expect an exception here + + + /** + * Consumes the remaining content of a streamed entity. + * This method is called to indicate that the content of this entity + * is no longer required. + * Streamed entities should dispose of the remaining content, if any. + * Self-contained entities can release allocated resources, but + * are not required to do anything. + * Wrapping entities should delegate this call to the wrapped entity. + *
+ * This method is of particular importance for entities being + * received from a {@link HttpConnection connection}. The entity + * needs to be consumed completely in order to re-use the connection + * with keep-alive. + * + * @throws IOException if an I/O error occurs. + * This indicates that connection keep-alive is not possible. + */ + public void consumeContent() throws IOException; + + +} // interface HttpEntity Index: trunk/http-core/src/java/org/apache/http/entity/BufferedHttpEntity.java =================================================================== --- trunk/http-core/src/java/org/apache/http/entity/BufferedHttpEntity.java (revision 374856) +++ trunk/http-core/src/java/org/apache/http/entity/BufferedHttpEntity.java (working copy) @@ -39,25 +39,26 @@ import org.apache.http.util.EntityUtils; /** - *

- *

+ * A wrapping entity that buffers it content if necessary. + * The buffered entity is always repeatable. + * If the wrapped entity is repeatable itself, calls are passed through. + * If the wrapped entity is not repeatable, the content is read into a + * buffer once and provided from there as often as required. + * * @author Oleg Kalnichevski * * @version $Revision$ * * @since 4.0 */ -public class BufferedHttpEntity implements HttpEntity { +public class BufferedHttpEntity extends HttpEntityWrapper + implements HttpEntity { - private final HttpEntity source; private final byte[] buffer; public BufferedHttpEntity(final HttpEntity entity) throws IOException { - super(); - if (entity == null) { - throw new IllegalArgumentException("HTTP entity may not be null"); - } - this.source = entity; + super(entity); + if (entity.isChunked() || !entity.isRepeatable() ) { this.buffer = EntityUtils.toByteArray(entity); } else { @@ -69,33 +70,36 @@ if (this.buffer != null) { return this.buffer.length; } else { - return this.source.getContentLength(); + return wrappedEntity.getContentLength(); } } - - public Header getContentType() { - return this.source.getContentType(); - } - public Header getContentEncoding() { - return this.source.getContentEncoding(); - } - public InputStream getContent() throws IOException { if (this.buffer != null) { return new ByteArrayInputStream(this.buffer); } else { - return this.source.getContent(); + return wrappedEntity.getContent(); } } - + + /** + * Tells that this entity does not have to be chunked. + * + * @return false + */ public boolean isChunked() { return false; } + /** + * Tells that this entity is repeatable. + * + * @return true + */ public boolean isRepeatable() { return true; } + public boolean writeTo(final OutputStream outstream) throws IOException { if (outstream == null) { @@ -105,8 +109,14 @@ outstream.write(this.buffer); return true; } else { - return this.source.writeTo(outstream); + return wrappedEntity.writeTo(outstream); } } + + + // non-javadoc, see interface HttpEntity + public boolean isStreaming() { + return (buffer == null) && wrappedEntity.isStreaming(); + } -} +} // class BufferedHttpEntity Index: trunk/http-core/src/java/org/apache/http/entity/ByteArrayEntity.java =================================================================== --- trunk/http-core/src/java/org/apache/http/entity/ByteArrayEntity.java (revision 374856) +++ trunk/http-core/src/java/org/apache/http/entity/ByteArrayEntity.java (working copy) @@ -36,23 +36,20 @@ import org.apache.http.Header; import org.apache.http.HttpEntity; -import org.apache.http.protocol.HTTP; + /** - *

- *

+ * A self-contained entity obtaining content from a byte array. + * * @author Oleg Kalnichevski * * @version $Revision$ * * @since 4.0 */ -public class ByteArrayEntity implements HttpEntity { +public class ByteArrayEntity extends AbstractHttpEntity implements HttpEntity { private final byte[] content; - private String contentType = HTTP.DEFAULT_CONTENT_TYPE; - private String contentEncoding = null; - private boolean chunked = false; public ByteArrayEntity(final byte[] b) { super(); @@ -66,42 +63,10 @@ return true; } - public boolean isChunked() { - return this.chunked; - } - - public void setChunked(boolean b) { - this.chunked = b; - } - public long getContentLength() { return this.content.length; } - - public Header getContentType() { - if (this.contentType != null) { - return new Header(HTTP.CONTENT_TYPE, this.contentType); - } else { - return null; - } - } - public void setContentType(final String contentType) { - this.contentType = contentType; - } - - public Header getContentEncoding() { - if (this.contentEncoding != null) { - return new Header(HTTP.CONTENT_ENCODING, this.contentEncoding); - } else { - return null; - } - } - - public void setContentEncoding(final String contentEncoding) { - this.contentEncoding = contentEncoding; - } - public InputStream getContent() { return new ByteArrayInputStream(this.content); } @@ -115,4 +80,15 @@ return true; } -} + + /** + * Tells that this entity is not streaming. + * + * @return false + */ + public boolean isStreaming() { + return false; + } + + +} // class ByteArrayEntity Index: trunk/http-core/src/java/org/apache/http/entity/StringEntity.java =================================================================== --- trunk/http-core/src/java/org/apache/http/entity/StringEntity.java (revision 374856) +++ trunk/http-core/src/java/org/apache/http/entity/StringEntity.java (working copy) @@ -39,21 +39,19 @@ import org.apache.http.HttpEntity; import org.apache.http.protocol.HTTP; + /** - *

- *

+ * A self-contained entity obtaining content from a string. + * * @author Oleg Kalnichevski * * @version $Revision$ * * @since 4.0 */ -public class StringEntity implements HttpEntity { +public class StringEntity extends AbstractHttpEntity implements HttpEntity { private final byte[] content; - private String contentType = null; - private String contentEncoding = null; - private boolean chunked = false; public StringEntity(final String s, String charset) throws UnsupportedEncodingException { @@ -64,8 +62,8 @@ if (charset == null) { charset = HTTP.DEFAULT_CONTENT_CHARSET; } - this.contentType = HTTP.PLAIN_TEXT_TYPE + HTTP.CHARSET_PARAM + charset; this.content = s.getBytes(charset); + setContentType(HTTP.PLAIN_TEXT_TYPE + HTTP.CHARSET_PARAM + charset); } public StringEntity(final String s) @@ -77,42 +75,10 @@ return true; } - public boolean isChunked() { - return this.chunked; - } - - public void setChunked(boolean b) { - this.chunked = b; - } - public long getContentLength() { return this.content.length; } - public Header getContentType() { - if (this.contentType != null) { - return new Header(HTTP.CONTENT_TYPE, this.contentType); - } else { - return null; - } - } - - public void setContentType(final String contentType) { - this.contentType = contentType; - } - - public Header getContentEncoding() { - if (this.contentEncoding != null) { - return new Header(HTTP.CONTENT_ENCODING, this.contentEncoding); - } else { - return null; - } - } - - public void setContentEncoding(final String contentEncoding) { - this.contentEncoding = contentEncoding; - } - public InputStream getContent() throws IOException { return new ByteArrayInputStream(this.content); } @@ -126,4 +92,15 @@ return true; } -} + + /** + * Tells that this entity is not streaming. + * + * @return false + */ + public boolean isStreaming() { + return false; + } + + +} // class StringEntity Index: trunk/http-core/src/java/org/apache/http/entity/InputStreamEntity.java =================================================================== --- trunk/http-core/src/java/org/apache/http/entity/InputStreamEntity.java (revision 374856) +++ trunk/http-core/src/java/org/apache/http/entity/InputStreamEntity.java (working copy) @@ -35,26 +35,25 @@ import org.apache.http.Header; import org.apache.http.HttpEntity; -import org.apache.http.protocol.HTTP; + /** - *

- *

+ * A streamed entity obtaining content from an {@link InputStream InputStream}. + * * @author Oleg Kalnichevski * * @version $Revision$ * * @since 4.0 */ -public class InputStreamEntity implements HttpEntity { +public class InputStreamEntity extends AbstractHttpEntity + implements HttpEntity { private final static int BUFFER_SIZE = 2048; private final InputStream content; private final long length; - private String contentType = HTTP.DEFAULT_CONTENT_TYPE; - private String contentEncoding = null; - private boolean chunked = false; + private boolean consumed = false; public InputStreamEntity(final InputStream instream, long length) { super(); @@ -69,42 +68,10 @@ return false; } - public boolean isChunked() { - return this.chunked; - } - - public void setChunked(boolean b) { - this.chunked = b; - } - public long getContentLength() { return this.length; } - - public Header getContentType() { - if (this.contentType != null) { - return new Header(HTTP.CONTENT_TYPE, this.contentType); - } else { - return null; - } - } - public void setContentType(final String contentType) { - this.contentType = contentType; - } - - public Header getContentEncoding() { - if (this.contentEncoding != null) { - return new Header(HTTP.CONTENT_ENCODING, this.contentEncoding); - } else { - return null; - } - } - - public void setContentEncoding(final String contentEncoding) { - this.contentEncoding = contentEncoding; - } - public InputStream getContent() throws IOException { return this.content; } @@ -126,5 +93,19 @@ } return true; } + + + // non-javadoc, see interface HttpEntity + public boolean isStreaming() { + return !consumed; + } + + // non-javadoc, see interface HttpEntity + public void consumeContent() throws IOException { + consumed = true; + // If the input stream is from a connection, closing it will read to + // the end of the content. Otherwise, we don't care what it does. + content.close(); + } -} +} // class InputStreamEntity Index: trunk/http-core/src/java/org/apache/http/entity/FileEntity.java =================================================================== --- trunk/http-core/src/java/org/apache/http/entity/FileEntity.java (revision 374856) +++ trunk/http-core/src/java/org/apache/http/entity/FileEntity.java (working copy) @@ -37,23 +37,20 @@ import org.apache.http.Header; import org.apache.http.HttpEntity; -import org.apache.http.protocol.HTTP; + /** - *

- *

+ * A self-contained entity obtaining content from a file. + * * @author Oleg Kalnichevski * * @version $Revision$ * * @since 4.0 */ -public class FileEntity implements HttpEntity { +public class FileEntity extends AbstractHttpEntity implements HttpEntity { private final File file; - private String contentType = null; - private String contentEncoding = null; - private boolean chunked = false; public FileEntity(final File file, final String contentType) { super(); @@ -61,49 +58,17 @@ throw new IllegalArgumentException("File may not be null"); } this.file = file; - this.contentType = contentType; + setContentType(contentType); } public boolean isRepeatable() { return true; } - public boolean isChunked() { - return this.chunked; - } - - public void setChunked(boolean b) { - this.chunked = b; - } - public long getContentLength() { return this.file.length(); } - public Header getContentType() { - if (this.contentType != null) { - return new Header(HTTP.CONTENT_TYPE, this.contentType); - } else { - return null; - } - } - - public void setContentType(final String contentType) { - this.contentType = contentType; - } - - public Header getContentEncoding() { - if (this.contentEncoding != null) { - return new Header(HTTP.CONTENT_ENCODING, this.contentEncoding); - } else { - return null; - } - } - - public void setContentEncoding(final String contentEncoding) { - this.contentEncoding = contentEncoding; - } - public InputStream getContent() throws IOException { return new FileInputStream(this.file); } @@ -122,4 +87,15 @@ return true; } -} + + /** + * Tells that this entity is not streaming. + * + * @return false + */ + public boolean isStreaming() { + return false; + } + + +} // class FileEntity Index: trunk/http-core/src/java/org/apache/http/entity/BasicHttpEntity.java =================================================================== --- trunk/http-core/src/java/org/apache/http/entity/BasicHttpEntity.java (revision 374856) +++ trunk/http-core/src/java/org/apache/http/entity/BasicHttpEntity.java (working copy) @@ -116,5 +116,19 @@ } return true; } + + + // non-javadoc, see interface HttpEntity + public boolean isStreaming() { + return (content != null); + } + + // non-javadoc, see interface HttpEntity + public void consumeContent() throws IOException { + if (content != null) { + content.close(); // reads to the end of the entity + content = null; + } + } -} +} // class BasicHttpEntity