|
16 | 16 |
|
17 | 17 | package com.google.cloud.storage; |
18 | 18 |
|
19 | | -import static com.google.cloud.RetryHelper.runWithRetries; |
20 | | - |
21 | | -import com.google.api.client.util.Preconditions; |
22 | | -import com.google.api.gax.retrying.ResultRetryAlgorithm; |
23 | 19 | import com.google.api.services.storage.model.StorageObject; |
24 | 20 | import com.google.cloud.ReadChannel; |
25 | 21 | import com.google.cloud.RestorableState; |
26 | | -import com.google.cloud.RetryHelper; |
27 | | -import com.google.cloud.Tuple; |
| 22 | +import com.google.cloud.storage.BlobReadChannelV2.BlobReadChannelContext; |
28 | 23 | import com.google.cloud.storage.spi.v1.StorageRpc; |
| 24 | +import com.google.common.annotations.VisibleForTesting; |
29 | 25 | import com.google.common.base.MoreObjects; |
30 | 26 | import java.io.IOException; |
31 | 27 | import java.io.Serializable; |
32 | 28 | import java.nio.ByteBuffer; |
33 | | -import java.nio.channels.ClosedChannelException; |
34 | 29 | import java.util.Map; |
35 | 30 | import java.util.Objects; |
36 | 31 |
|
37 | | -/** Default implementation for ReadChannel. */ |
| 32 | +/** |
| 33 | + * Hierarchy retained for {@link RestorableState#restore()}. Will be removed in next major version! |
| 34 | + */ |
| 35 | +@Deprecated |
38 | 36 | class BlobReadChannel implements ReadChannel { |
39 | 37 |
|
40 | | - private static final int DEFAULT_CHUNK_SIZE = 2 * 1024 * 1024; |
41 | | - |
42 | | - private final HttpStorageOptions serviceOptions; |
43 | | - private final BlobId blob; |
44 | | - private final Map<StorageRpc.Option, ?> requestOptions; |
45 | | - private final HttpRetryAlgorithmManager retryAlgorithmManager; |
46 | | - private String lastEtag; |
47 | | - private long position; |
48 | | - private boolean isOpen; |
49 | | - private boolean endOfStream; |
50 | | - private int chunkSize = DEFAULT_CHUNK_SIZE; |
51 | | - |
52 | | - private final StorageRpc storageRpc; |
53 | | - private final StorageObject storageObject; |
54 | | - private int bufferPos; |
55 | | - private byte[] buffer; |
56 | | - private long limit; |
57 | | - |
58 | | - BlobReadChannel( |
59 | | - HttpStorageOptions serviceOptions, BlobId blob, Map<StorageRpc.Option, ?> requestOptions) { |
60 | | - this.serviceOptions = serviceOptions; |
61 | | - this.blob = blob; |
62 | | - this.requestOptions = requestOptions; |
63 | | - this.retryAlgorithmManager = serviceOptions.getRetryAlgorithmManager(); |
64 | | - isOpen = true; |
65 | | - storageRpc = serviceOptions.getStorageRpcV1(); |
66 | | - storageObject = Conversions.apiary().blobId().encode(blob); |
67 | | - this.limit = Long.MAX_VALUE; |
68 | | - } |
| 38 | + private BlobReadChannel() {} |
69 | 39 |
|
70 | 40 | @Override |
71 | 41 | public RestorableState<ReadChannel> capture() { |
72 | | - StateImpl.Builder builder = |
73 | | - StateImpl.builder(serviceOptions, blob, requestOptions) |
74 | | - .setPosition(position) |
75 | | - .setIsOpen(isOpen) |
76 | | - .setEndOfStream(endOfStream) |
77 | | - .setChunkSize(chunkSize) |
78 | | - .setLimit(limit); |
79 | | - if (buffer != null) { |
80 | | - builder.setPosition(position + bufferPos); |
81 | | - builder.setEndOfStream(false); |
82 | | - } |
83 | | - return builder.build(); |
| 42 | + throw new IllegalStateException("Illegal method access"); |
84 | 43 | } |
85 | 44 |
|
86 | 45 | @Override |
87 | 46 | public boolean isOpen() { |
88 | | - return isOpen; |
| 47 | + throw new IllegalStateException("Illegal method access"); |
89 | 48 | } |
90 | 49 |
|
91 | 50 | @Override |
92 | 51 | public void close() { |
93 | | - if (isOpen) { |
94 | | - buffer = null; |
95 | | - isOpen = false; |
96 | | - } |
97 | | - } |
98 | | - |
99 | | - private void validateOpen() throws ClosedChannelException { |
100 | | - if (!isOpen) { |
101 | | - throw new ClosedChannelException(); |
102 | | - } |
| 52 | + throw new IllegalStateException("Illegal method access"); |
103 | 53 | } |
104 | 54 |
|
105 | 55 | @Override |
106 | 56 | public void seek(long position) throws IOException { |
107 | | - validateOpen(); |
108 | | - this.position = position; |
109 | | - buffer = null; |
110 | | - bufferPos = 0; |
111 | | - endOfStream = false; |
| 57 | + throw new IllegalStateException("Illegal method access"); |
112 | 58 | } |
113 | 59 |
|
114 | 60 | @Override |
115 | 61 | public void setChunkSize(int chunkSize) { |
116 | | - this.chunkSize = chunkSize <= 0 ? DEFAULT_CHUNK_SIZE : chunkSize; |
| 62 | + throw new IllegalStateException("Illegal method access"); |
117 | 63 | } |
118 | 64 |
|
119 | 65 | @Override |
120 | 66 | public int read(ByteBuffer byteBuffer) throws IOException { |
121 | | - validateOpen(); |
122 | | - if (buffer == null) { |
123 | | - if (endOfStream) { |
124 | | - return -1; |
125 | | - } |
126 | | - final int toRead = |
127 | | - Math.toIntExact(Math.min(limit - position, Math.max(byteBuffer.remaining(), chunkSize))); |
128 | | - if (toRead <= 0) { |
129 | | - endOfStream = true; |
130 | | - return -1; |
131 | | - } |
132 | | - try { |
133 | | - ResultRetryAlgorithm<?> algorithm = |
134 | | - retryAlgorithmManager.getForObjectsGet(storageObject, requestOptions); |
135 | | - Tuple<String, byte[]> result = |
136 | | - runWithRetries( |
137 | | - () -> storageRpc.read(storageObject, requestOptions, position, toRead), |
138 | | - serviceOptions.getRetrySettings(), |
139 | | - algorithm, |
140 | | - serviceOptions.getClock()); |
141 | | - String etag = result.x(); |
142 | | - byte[] bytes = result.y(); |
143 | | - if (bytes.length > 0 && lastEtag != null && !Objects.equals(etag, lastEtag)) { |
144 | | - throw new IOException("Blob " + blob + " was updated while reading"); |
145 | | - } |
146 | | - lastEtag = etag; |
147 | | - buffer = bytes; |
148 | | - } catch (RetryHelper.RetryHelperException e) { |
149 | | - throw new IOException(e.getCause()); |
150 | | - } |
151 | | - if (toRead > buffer.length) { |
152 | | - endOfStream = true; |
153 | | - if (buffer.length == 0) { |
154 | | - buffer = null; |
155 | | - return -1; |
156 | | - } |
157 | | - } |
158 | | - } |
159 | | - int toWrite = Math.min(buffer.length - bufferPos, byteBuffer.remaining()); |
160 | | - byteBuffer.put(buffer, bufferPos, toWrite); |
161 | | - bufferPos += toWrite; |
162 | | - if (bufferPos >= buffer.length) { |
163 | | - position += buffer.length; |
164 | | - buffer = null; |
165 | | - bufferPos = 0; |
166 | | - } |
167 | | - return toWrite; |
| 67 | + throw new IllegalStateException("Illegal method access"); |
168 | 68 | } |
169 | 69 |
|
170 | 70 | @Override |
171 | 71 | public ReadChannel limit(long limit) { |
172 | | - Preconditions.checkArgument(limit >= 0, "Limit must be >= 0"); |
173 | | - this.limit = limit; |
174 | | - return this; |
| 72 | + throw new IllegalStateException("Illegal method access"); |
175 | 73 | } |
176 | 74 |
|
177 | 75 | @Override |
178 | 76 | public long limit() { |
179 | | - return limit; |
| 77 | + throw new IllegalStateException("Illegal method access"); |
180 | 78 | } |
181 | 79 |
|
| 80 | + /** Retained for binary compatibility. Will be removed at next major version! */ |
| 81 | + @SuppressWarnings("unused") |
| 82 | + @Deprecated |
| 83 | + @VisibleForTesting |
182 | 84 | static class StateImpl implements RestorableState<ReadChannel>, Serializable { |
183 | 85 |
|
184 | 86 | private static final long serialVersionUID = 7784852608213694645L; |
185 | 87 |
|
186 | | - private final HttpStorageOptions serviceOptions; |
187 | | - private final BlobId blob; |
188 | | - private final Map<StorageRpc.Option, ?> requestOptions; |
189 | | - private final String lastEtag; |
190 | | - private final long position; |
191 | | - private final boolean isOpen; |
192 | | - private final boolean endOfStream; |
193 | | - private final int chunkSize; |
194 | | - private final long limit; |
195 | | - |
196 | | - StateImpl(Builder builder) { |
197 | | - this.serviceOptions = builder.serviceOptions; |
198 | | - this.blob = builder.blob; |
199 | | - this.requestOptions = builder.requestOptions; |
200 | | - this.lastEtag = builder.lastEtag; |
201 | | - this.position = builder.position; |
202 | | - this.isOpen = builder.isOpen; |
203 | | - this.endOfStream = builder.endOfStream; |
204 | | - this.chunkSize = builder.chunkSize; |
205 | | - this.limit = builder.limit; |
206 | | - } |
207 | | - |
208 | | - static class Builder { |
209 | | - private final HttpStorageOptions serviceOptions; |
210 | | - private final BlobId blob; |
211 | | - private final Map<StorageRpc.Option, ?> requestOptions; |
212 | | - private String lastEtag; |
213 | | - private long position; |
214 | | - private boolean isOpen; |
215 | | - private boolean endOfStream; |
216 | | - private int chunkSize; |
217 | | - private long limit; |
218 | | - |
219 | | - private Builder( |
220 | | - HttpStorageOptions options, BlobId blob, Map<StorageRpc.Option, ?> reqOptions) { |
221 | | - this.serviceOptions = options; |
222 | | - this.blob = blob; |
223 | | - this.requestOptions = reqOptions; |
224 | | - } |
225 | | - |
226 | | - Builder setLastEtag(String lastEtag) { |
227 | | - this.lastEtag = lastEtag; |
228 | | - return this; |
229 | | - } |
230 | | - |
231 | | - Builder setPosition(long position) { |
232 | | - this.position = position; |
233 | | - return this; |
234 | | - } |
235 | | - |
236 | | - Builder setIsOpen(boolean isOpen) { |
237 | | - this.isOpen = isOpen; |
238 | | - return this; |
239 | | - } |
240 | | - |
241 | | - Builder setEndOfStream(boolean endOfStream) { |
242 | | - this.endOfStream = endOfStream; |
243 | | - return this; |
244 | | - } |
245 | | - |
246 | | - Builder setChunkSize(int chunkSize) { |
247 | | - this.chunkSize = chunkSize; |
248 | | - return this; |
249 | | - } |
250 | | - |
251 | | - Builder setLimit(long limit) { |
252 | | - this.limit = limit; |
253 | | - return this; |
254 | | - } |
| 88 | + // the following fields are dangling as they are only set via object deserialization, and only |
| 89 | + // read in #restore() |
| 90 | + private HttpStorageOptions serviceOptions; |
| 91 | + private BlobId blob; |
| 92 | + private Map<StorageRpc.Option, ?> requestOptions; |
| 93 | + private String lastEtag; |
| 94 | + private long position; |
| 95 | + private boolean isOpen; |
| 96 | + private boolean endOfStream; |
| 97 | + private int chunkSize; |
| 98 | + private long limit; |
255 | 99 |
|
256 | | - RestorableState<ReadChannel> build() { |
257 | | - return new StateImpl(this); |
258 | | - } |
259 | | - } |
260 | | - |
261 | | - static Builder builder( |
262 | | - HttpStorageOptions options, BlobId blob, Map<StorageRpc.Option, ?> reqOptions) { |
263 | | - return new Builder(options, blob, reqOptions); |
264 | | - } |
| 100 | + private StateImpl() {} |
265 | 101 |
|
266 | 102 | @Override |
267 | 103 | public ReadChannel restore() { |
268 | | - BlobReadChannel channel = new BlobReadChannel(serviceOptions, blob, requestOptions); |
269 | | - channel.lastEtag = lastEtag; |
270 | | - channel.position = position; |
271 | | - channel.isOpen = isOpen; |
272 | | - channel.endOfStream = endOfStream; |
273 | | - channel.chunkSize = chunkSize; |
274 | | - channel.limit = limit; |
| 104 | + StorageObject encode = Conversions.apiary().blobId().encode(blob); |
| 105 | + BlobReadChannelV2 channel = |
| 106 | + new BlobReadChannelV2( |
| 107 | + encode, requestOptions, BlobReadChannelContext.from(serviceOptions)); |
| 108 | + try { |
| 109 | + channel.seek(position); |
| 110 | + channel.limit(limit); |
| 111 | + channel.setChunkSize(chunkSize); |
| 112 | + if (!isOpen) { |
| 113 | + channel.close(); |
| 114 | + } |
| 115 | + } catch (IOException e) { |
| 116 | + throw StorageException.coalesce(e); |
| 117 | + } |
275 | 118 | return channel; |
276 | 119 | } |
277 | 120 |
|
|
0 commit comments