1.9.30 sync.
[gae.git] / java / src / main / com / google / appengine / api / blobstore / BlobstoreInputStream.java
blob7bea364bc317b54a5ad4ff2798b28cf80d19ffce
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;
10 /**
11 * BlobstoreInputStream provides an InputStream view of a blob in
12 * Blobstore.
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 {
20 /**
21 * A subclass of {@link IOException } that indicates operations on a stream after
22 * it is closed.
24 public static final class ClosedStreamException extends IOException {
25 /**
26 * Construct an exception with specified message.
28 public ClosedStreamException(String message) {
29 super(message);
32 /**
33 * Construct exception with specified message and cause.
35 public ClosedStreamException(String message, Throwable cause) {
36 super(message, cause);
40 /**
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 {
45 /**
46 * Constructs a {@code BlobstoreIOException} with the specified detail
47 * message.
49 public BlobstoreIOException(String message) {
50 super(message);
53 /**
54 * Constructs a {@code BlobstoreIOException} with the specified detail
55 * message and cause.
57 public BlobstoreIOException(String message, Throwable cause) {
58 super(message);
59 initCause(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;
81 /**
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());
95 /**
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 {
104 this(blobKey, 0);
107 BlobstoreInputStream(BlobKey blobKey,
108 long offset,
109 BlobInfoFactory blobInfoFactory,
110 BlobstoreService blobstoreService) throws IOException {
111 if (offset < 0) {
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
129 * yet been read.
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 {
138 if (isClosed) {
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
146 * warning).
148 @Override
149 public void close() throws IOException {
150 isClosed = true;
151 buffer = null;
154 @Override
155 public int read() throws IOException {
156 checkClosedStream();
158 if (!ensureDataInBuffer()) {
159 return -1;
162 return buffer[bufferOffset++] & 0xff;
165 @Override
166 public int read(byte[] b, int off, int len) throws IOException {
167 checkClosedStream();
168 Preconditions.checkNotNull(b);
169 Preconditions.checkElementIndex(off, b.length);
170 Preconditions.checkPositionIndex(off + len, b.length);
171 if (len == 0) {
172 return 0;
175 if (!ensureDataInBuffer()) {
176 return -1;
179 int amountToCopy = Math.min(buffer.length - bufferOffset, len);
180 System.arraycopy(buffer, bufferOffset, b, off, amountToCopy);
181 bufferOffset += amountToCopy;
182 return amountToCopy;
185 @Override
186 public boolean markSupported() {
187 return true;
190 @Override
191 public void mark(int readlimit) {
192 markSet = true;
193 markOffset = blobOffset;
194 if (buffer != null) {
195 markOffset += bufferOffset - buffer.length;
199 @Override
200 public void reset() throws IOException {
201 checkClosedStream();
202 if (!markSet) {
203 throw new IOException("Attempted to reset on un-mark()ed BlobstoreInputStream");
205 blobOffset = markOffset;
206 buffer = null;
207 bufferOffset = 0;
208 markSet = false;
212 * Attempts to ensure that {@code buffer} contains unprocessed data from the
213 * blob.
215 * @return {@code true} if the buffer now contains unprocessed data.
216 * @throws BlobstoreIOException if there is a problem retrieving data from
217 * the blob.
219 private boolean ensureDataInBuffer() throws IOException {
220 if (!atEndOfBuffer()) {
221 return true;
224 long fetchSize = Math.min(
225 blobInfo.getSize() - blobOffset,
226 BlobstoreService.MAX_BLOB_FETCH_SIZE);
227 if (fetchSize <= 0) {
228 buffer = null;
229 return false;
231 try {
232 buffer = blobstoreService.fetchData(blobKey, blobOffset, blobOffset + fetchSize - 1);
233 blobOffset += buffer.length;
234 bufferOffset = 0;
235 return true;
236 } catch (BlobstoreFailureException bfe) {
237 throw new BlobstoreIOException("Error reading data from Blobstore", bfe);