App Engine Java SDK version 1.7.0
[gae.git] / java / src / main / com / google / appengine / api / blobstore / BlobstoreInputStream.java
blobe772514410e6b1389863226e06e34e913f0d67a5
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");
143 @Override
144 public void close() throws IOException {
145 isClosed = true;
146 buffer = null;
149 @Override
150 public int read() throws IOException {
151 checkClosedStream();
153 if (!ensureDataInBuffer()) {
154 return -1;
157 return buffer[bufferOffset++] & 0xff;
160 @Override
161 public int read(byte[] b, int off, int len) throws IOException {
162 checkClosedStream();
163 Preconditions.checkNotNull(b);
164 Preconditions.checkElementIndex(off, b.length);
165 Preconditions.checkPositionIndex(off + len, b.length);
166 if (len == 0) {
167 return 0;
170 if (!ensureDataInBuffer()) {
171 return -1;
174 int amountToCopy = Math.min(buffer.length - bufferOffset, len);
175 System.arraycopy(buffer, bufferOffset, b, off, amountToCopy);
176 bufferOffset += amountToCopy;
177 return amountToCopy;
180 @Override
181 public boolean markSupported() {
182 return true;
185 @Override
186 public void mark(int readlimit) {
187 markSet = true;
188 markOffset = blobOffset;
189 if (buffer != null) {
190 markOffset += bufferOffset - buffer.length;
194 @Override
195 public void reset() throws IOException {
196 checkClosedStream();
197 if (!markSet) {
198 throw new IOException("Attempted to reset on un-mark()ed BlobstoreInputStream");
200 blobOffset = markOffset;
201 buffer = null;
202 bufferOffset = 0;
203 markSet = false;
207 * Attempts to ensure that {@code buffer} contains unprocessed data from the
208 * blob.
210 * @return {@code true} if the buffer now contains unprocessed data.
211 * @throws BlobstoreIOException if there is a problem retrieving data from
212 * the blob.
214 private boolean ensureDataInBuffer() throws IOException {
215 if (!atEndOfBuffer()) {
216 return true;
219 long fetchSize = Math.min(
220 blobInfo.getSize() - blobOffset,
221 BlobstoreService.MAX_BLOB_FETCH_SIZE);
222 if (fetchSize <= 0) {
223 buffer = null;
224 return false;
226 try {
227 buffer = blobstoreService.fetchData(blobKey, blobOffset, blobOffset + fetchSize - 1);
228 blobOffset += buffer.length;
229 bufferOffset = 0;
230 return true;
231 } catch (BlobstoreFailureException bfe) {
232 throw new BlobstoreIOException("Error reading data from Blobstore", bfe);