1 // Copyright 2010 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.api
.blobstore
;
5 import com
.google
.common
.base
.Preconditions
;
7 import java
.io
.IOException
;
8 import java
.io
.InputStream
;
11 * BlobstoreInputStream provides an InputStream view of a blob in
14 * It is thread compatible but not thread safe: there is no static state, but
15 * any multithreaded use must be externally synchronized.
18 public final class BlobstoreInputStream
extends InputStream
{
21 * A subclass of {@link IOException } that indicates operations on a stream after
24 public static final class ClosedStreamException
extends IOException
{
26 * Construct an exception with specified message.
28 public ClosedStreamException(String message
) {
33 * Construct exception with specified message and cause.
35 public ClosedStreamException(String message
, Throwable cause
) {
36 super(message
, cause
);
41 * A subclass of {@link IOException} that indicates that there was a problem
42 * interacting with Blobstore.
44 public static final class BlobstoreIOException
extends IOException
{
46 * Constructs a {@code BlobstoreIOException} with the specified detail
49 public BlobstoreIOException(String message
) {
54 * Constructs a {@code BlobstoreIOException} with the specified detail
57 public BlobstoreIOException(String message
, Throwable cause
) {
63 private final BlobKey blobKey
;
65 private final BlobInfo blobInfo
;
67 private long blobOffset
;
69 private byte[] buffer
;
71 private int bufferOffset
;
73 private boolean markSet
= false;
75 private long markOffset
;
77 private final BlobstoreService blobstoreService
;
79 private boolean isClosed
= false;
82 * Creates a BlobstoreInputStream that reads data from the blob indicated by
83 * blobKey, starting at offset.
85 * @param blobKey A valid BlobKey indicating the blob to read from.
86 * @param offset An offset to start from.
88 * @throws BlobstoreIOException If the blobKey given is invalid.
89 * @throws IllegalArgumentException If {@code offset} < 0.
91 public BlobstoreInputStream(BlobKey blobKey
, long offset
) throws IOException
{
92 this(blobKey
, offset
, new BlobInfoFactory(), BlobstoreServiceFactory
.getBlobstoreService());
96 * Creates a BlobstoreInputStream that reads data from the blob indicated by
97 * blobKey, starting at the beginning of the blob.
99 * @param blobKey A valid BlobKey indicating the blob to read from.
100 * @throws BlobstoreIOException If the blobKey given is invalid.
101 * @throws IllegalArgumentException If {@code offset} < 0.
103 public BlobstoreInputStream(BlobKey blobKey
) throws IOException
{
107 BlobstoreInputStream(BlobKey blobKey
,
109 BlobInfoFactory blobInfoFactory
,
110 BlobstoreService blobstoreService
) throws IOException
{
112 throw new IllegalArgumentException("Offset " + offset
+ " is less than 0");
115 this.blobKey
= blobKey
;
116 this.blobOffset
= offset
;
117 this.blobstoreService
= blobstoreService
;
118 blobInfo
= blobInfoFactory
.loadBlobInfo(blobKey
);
119 if (blobInfo
== null) {
120 throw new BlobstoreIOException("BlobstoreInputStream received an invalid blob key: "
121 + blobKey
.getKeyString());
126 * Check if we have entirely consumed the last buffer read from the blob.
128 * @returns true if we have consumed the last buffer or if no buffer has
131 private boolean atEndOfBuffer() {
132 Preconditions
.checkState(buffer
== null || bufferOffset
<= buffer
.length
,
133 "Buffer offset is past the end of the buffer. This should never happen.");
134 return buffer
== null || bufferOffset
== buffer
.length
;
137 private void checkClosedStream() throws ClosedStreamException
{
139 throw new ClosedStreamException("Stream is closed");
144 * @throws IOException - does not actually throw but as it's part of our public API and
145 * removing it can cause compilation errors, leaving it in (and documenting to quiet Eclipse
149 public void close() throws IOException
{
155 public int read() throws IOException
{
158 if (!ensureDataInBuffer()) {
162 return buffer
[bufferOffset
++] & 0xff;
166 public int read(byte[] b
, int off
, int len
) throws IOException
{
168 Preconditions
.checkNotNull(b
);
169 Preconditions
.checkElementIndex(off
, b
.length
);
170 Preconditions
.checkPositionIndex(off
+ len
, b
.length
);
175 if (!ensureDataInBuffer()) {
179 int amountToCopy
= Math
.min(buffer
.length
- bufferOffset
, len
);
180 System
.arraycopy(buffer
, bufferOffset
, b
, off
, amountToCopy
);
181 bufferOffset
+= amountToCopy
;
186 public boolean markSupported() {
191 public void mark(int readlimit
) {
193 markOffset
= blobOffset
;
194 if (buffer
!= null) {
195 markOffset
+= bufferOffset
- buffer
.length
;
200 public void reset() throws IOException
{
203 throw new IOException("Attempted to reset on un-mark()ed BlobstoreInputStream");
205 blobOffset
= markOffset
;
212 * Attempts to ensure that {@code buffer} contains unprocessed data from the
215 * @return {@code true} if the buffer now contains unprocessed data.
216 * @throws BlobstoreIOException if there is a problem retrieving data from
219 private boolean ensureDataInBuffer() throws IOException
{
220 if (!atEndOfBuffer()) {
224 long fetchSize
= Math
.min(
225 blobInfo
.getSize() - blobOffset
,
226 BlobstoreService
.MAX_BLOB_FETCH_SIZE
);
227 if (fetchSize
<= 0) {
232 buffer
= blobstoreService
.fetchData(blobKey
, blobOffset
, blobOffset
+ fetchSize
- 1);
233 blobOffset
+= buffer
.length
;
236 } catch (BlobstoreFailureException bfe
) {
237 throw new BlobstoreIOException("Error reading data from Blobstore", bfe
);