1919import static com .google .cloud .storage .ByteSizeConstants ._256KiB ;
2020import static java .util .Objects .requireNonNull ;
2121
22+ import com .google .api .core .ApiFutures ;
2223import com .google .api .core .BetaApi ;
2324import com .google .api .core .InternalApi ;
25+ import com .google .api .gax .retrying .BasicResultRetryAlgorithm ;
26+ import com .google .api .gax .rpc .AbortedException ;
27+ import com .google .api .gax .rpc .ApiException ;
28+ import com .google .cloud .storage .BlobAppendableUpload .AppendableUploadWriteableByteChannel ;
29+ import com .google .cloud .storage .BlobAppendableUploadImpl .AppendableObjectBufferedWritableByteChannel ;
2430import com .google .cloud .storage .Storage .BlobWriteOption ;
2531import com .google .cloud .storage .TransportCompatibility .Transport ;
32+ import com .google .cloud .storage .UnifiedOpts .ObjectTargetOpt ;
33+ import com .google .cloud .storage .UnifiedOpts .Opts ;
34+ import com .google .storage .v2 .BidiWriteObjectRequest ;
35+ import com .google .storage .v2 .BidiWriteObjectResponse ;
36+ import com .google .storage .v2 .Object ;
2637import javax .annotation .concurrent .Immutable ;
2738
2839/**
3950public final class BlobAppendableUploadConfig {
4051
4152 private static final BlobAppendableUploadConfig INSTANCE =
42- new BlobAppendableUploadConfig (FlushPolicy .minFlushSize (_256KiB ), Hasher .enabled ());
53+ new BlobAppendableUploadConfig (
54+ FlushPolicy .minFlushSize (_256KiB ),
55+ Hasher .enabled (),
56+ CloseAction .CLOSE_WITHOUT_FINALIZING );
4357
4458 private final FlushPolicy flushPolicy ;
4559 private final Hasher hasher ;
60+ private final CloseAction closeAction ;
4661
47- private BlobAppendableUploadConfig (FlushPolicy flushPolicy , Hasher hasher ) {
62+ private BlobAppendableUploadConfig (
63+ FlushPolicy flushPolicy , Hasher hasher , CloseAction closeAction ) {
4864 this .flushPolicy = flushPolicy ;
4965 this .hasher = hasher ;
66+ this .closeAction = closeAction ;
5067 }
5168
5269 /**
@@ -77,7 +94,37 @@ public BlobAppendableUploadConfig withFlushPolicy(FlushPolicy flushPolicy) {
7794 if (this .flushPolicy .equals (flushPolicy )) {
7895 return this ;
7996 }
80- return new BlobAppendableUploadConfig (flushPolicy , hasher );
97+ return new BlobAppendableUploadConfig (flushPolicy , hasher , closeAction );
98+ }
99+
100+ /**
101+ * The {@link CloseAction} which will dictate the behavior of {@link
102+ * AppendableUploadWriteableByteChannel#close()}.
103+ *
104+ * <p><i>Default:</i> {@link CloseAction#CLOSE_WITHOUT_FINALIZING}
105+ *
106+ * @see #withCloseAction(CloseAction)
107+ * @since 2.51.0 This new api is in preview and is subject to breaking changes.
108+ */
109+ @ BetaApi
110+ public CloseAction getCloseAction () {
111+ return closeAction ;
112+ }
113+
114+ /**
115+ * Return an instance with the {@code CloseAction} set to be the specified value. <i>Default:</i>
116+ * {@link CloseAction#CLOSE_WITHOUT_FINALIZING}
117+ *
118+ * @see #getCloseAction()
119+ * @since 2.51.0 This new api is in preview and is subject to breaking changes.
120+ */
121+ @ BetaApi
122+ public BlobAppendableUploadConfig withCloseAction (CloseAction closeAction ) {
123+ requireNonNull (closeAction , "closeAction must be non null" );
124+ if (this .closeAction == closeAction ) {
125+ return this ;
126+ }
127+ return new BlobAppendableUploadConfig (flushPolicy , hasher , closeAction );
81128 }
82129
83130 /**
@@ -108,7 +155,8 @@ BlobAppendableUploadConfig withCrc32cValidationEnabled(boolean enabled) {
108155 } else if (!enabled && Hasher .noop ().equals (hasher )) {
109156 return this ;
110157 }
111- return new BlobAppendableUploadConfig (flushPolicy , enabled ? Hasher .enabled () : Hasher .noop ());
158+ return new BlobAppendableUploadConfig (
159+ flushPolicy , enabled ? Hasher .enabled () : Hasher .noop (), closeAction );
112160 }
113161
114162 /** Never to be made public until {@link Hasher} is public */
@@ -125,6 +173,7 @@ Hasher getHasher() {
125173 * <pre>{@code
126174 * BlobAppendableUploadConfig.of()
127175 * .withFlushPolicy(FlushPolicy.minFlushSize(256 * 1024))
176+ * .withCloseAction(CloseAction.CLOSE_WITHOUT_FINALIZING)
128177 * }</pre>
129178 *
130179 * @since 2.51.0 This new api is in preview and is subject to breaking changes.
@@ -134,4 +183,89 @@ Hasher getHasher() {
134183 public static BlobAppendableUploadConfig of () {
135184 return INSTANCE ;
136185 }
186+
187+ /**
188+ * Enum providing the possible actions which can be taken during the {@link
189+ * AppendableUploadWriteableByteChannel#close()} call.
190+ *
191+ * @see AppendableUploadWriteableByteChannel#close()
192+ * @see BlobAppendableUploadConfig#withCloseAction(CloseAction)
193+ * @see BlobAppendableUploadConfig#getCloseAction()
194+ * @since 2.51.0 This new api is in preview and is subject to breaking changes.
195+ */
196+ @ BetaApi
197+ public enum CloseAction {
198+ /**
199+ * Designate that when {@link AppendableUploadWriteableByteChannel#close()} is called, the
200+ * appendable upload should be finalized.
201+ *
202+ * @since 2.51.0 This new api is in preview and is subject to breaking changes.
203+ * @see AppendableUploadWriteableByteChannel#finalizeAndClose()
204+ */
205+ @ BetaApi
206+ FINALIZE_WHEN_CLOSING ,
207+ /**
208+ * Designate that when {@link AppendableUploadWriteableByteChannel#close()} is called, the
209+ * appendable upload should NOT be finalized, allowing for takeover by another session or
210+ * client.
211+ *
212+ * @since 2.51.0 This new api is in preview and is subject to breaking changes.
213+ * @see AppendableUploadWriteableByteChannel#closeWithoutFinalizing()
214+ */
215+ @ BetaApi
216+ CLOSE_WITHOUT_FINALIZING
217+ }
218+
219+ BlobAppendableUpload create (GrpcStorageImpl storage , BlobInfo info , Opts <ObjectTargetOpt > opts ) {
220+ boolean takeOver = info .getGeneration () != null ;
221+ BidiWriteObjectRequest req =
222+ takeOver
223+ ? storage .getBidiWriteObjectRequestForTakeover (info , opts )
224+ : storage .getBidiWriteObjectRequest (info , opts );
225+
226+ BidiAppendableWrite baw = new BidiAppendableWrite (req , takeOver );
227+
228+ WritableByteChannelSession <AppendableObjectBufferedWritableByteChannel , BidiWriteObjectResponse >
229+ build =
230+ ResumableMedia .gapic ()
231+ .write ()
232+ .bidiByteChannel (storage .storageClient .bidiWriteObjectCallable ())
233+ .setHasher (this .getHasher ())
234+ .setByteStringStrategy (ByteStringStrategy .copy ())
235+ .appendable ()
236+ .withRetryConfig (
237+ storage .retrier .withAlg (
238+ new BasicResultRetryAlgorithm <Object >() {
239+ @ Override
240+ public boolean shouldRetry (
241+ Throwable previousThrowable , Object previousResponse ) {
242+ // TODO: remove this later once the redirects are not handled by the
243+ // retry loop
244+ ApiException apiEx = null ;
245+ if (previousThrowable instanceof StorageException ) {
246+ StorageException se = (StorageException ) previousThrowable ;
247+ Throwable cause = se .getCause ();
248+ if (cause instanceof ApiException ) {
249+ apiEx = (ApiException ) cause ;
250+ }
251+ }
252+ if (apiEx instanceof AbortedException ) {
253+ return true ;
254+ }
255+ return storage
256+ .retryAlgorithmManager
257+ .idempotent ()
258+ .shouldRetry (previousThrowable , null );
259+ }
260+ }))
261+ .buffered (this .getFlushPolicy ())
262+ .setStartAsync (ApiFutures .immediateFuture (baw ))
263+ .setGetCallable (storage .storageClient .getObjectCallable ())
264+ .setFinalizeOnClose (this .closeAction == CloseAction .FINALIZE_WHEN_CLOSING )
265+ .build ();
266+
267+ return new BlobAppendableUploadImpl (
268+ new DefaultBlobWriteSessionConfig .DecoratedWritableByteChannelSession <>(
269+ build , BidiBlobWriteSessionConfig .Factory .WRITE_OBJECT_RESPONSE_BLOB_INFO_DECODER ));
270+ }
137271}
0 commit comments