4242import java .nio .charset .StandardCharsets ;
4343import java .security .SecureRandom ;
4444import java .time .Clock ;
45+ import java .time .Duration ;
46+ import java .time .OffsetDateTime ;
4547import java .util .Base64 ;
4648import java .util .Base64 .Encoder ;
4749import java .util .concurrent .Executor ;
4850import java .util .concurrent .Executors ;
4951import java .util .concurrent .ThreadFactory ;
5052import java .util .concurrent .atomic .AtomicInteger ;
53+ import java .util .function .UnaryOperator ;
5154import javax .annotation .concurrent .Immutable ;
5255import org .checkerframework .checker .nullness .qual .NonNull ;
5356
@@ -125,18 +128,21 @@ public final class ParallelCompositeUploadBlobWriteSessionConfig extends BlobWri
125128 private final BufferAllocationStrategy bufferAllocationStrategy ;
126129 private final PartNamingStrategy partNamingStrategy ;
127130 private final PartCleanupStrategy partCleanupStrategy ;
131+ private final PartMetadataFieldDecorator partMetadataFieldDecorator ;
128132
129133 private ParallelCompositeUploadBlobWriteSessionConfig (
130134 int maxPartsPerCompose ,
131135 ExecutorSupplier executorSupplier ,
132136 BufferAllocationStrategy bufferAllocationStrategy ,
133137 PartNamingStrategy partNamingStrategy ,
134- PartCleanupStrategy partCleanupStrategy ) {
138+ PartCleanupStrategy partCleanupStrategy ,
139+ PartMetadataFieldDecorator partMetadataFieldDecorator ) {
135140 this .maxPartsPerCompose = maxPartsPerCompose ;
136141 this .executorSupplier = executorSupplier ;
137142 this .bufferAllocationStrategy = bufferAllocationStrategy ;
138143 this .partNamingStrategy = partNamingStrategy ;
139144 this .partCleanupStrategy = partCleanupStrategy ;
145+ this .partMetadataFieldDecorator = partMetadataFieldDecorator ;
140146 }
141147
142148 @ InternalApi
@@ -150,7 +156,8 @@ ParallelCompositeUploadBlobWriteSessionConfig withMaxPartsPerCompose(int maxPart
150156 executorSupplier ,
151157 bufferAllocationStrategy ,
152158 partNamingStrategy ,
153- partCleanupStrategy );
159+ partCleanupStrategy ,
160+ partMetadataFieldDecorator );
154161 }
155162
156163 /**
@@ -170,7 +177,8 @@ public ParallelCompositeUploadBlobWriteSessionConfig withExecutorSupplier(
170177 executorSupplier ,
171178 bufferAllocationStrategy ,
172179 partNamingStrategy ,
173- partCleanupStrategy );
180+ partCleanupStrategy ,
181+ partMetadataFieldDecorator );
174182 }
175183
176184 /**
@@ -191,7 +199,8 @@ public ParallelCompositeUploadBlobWriteSessionConfig withBufferAllocationStrateg
191199 executorSupplier ,
192200 bufferAllocationStrategy ,
193201 partNamingStrategy ,
194- partCleanupStrategy );
202+ partCleanupStrategy ,
203+ partMetadataFieldDecorator );
195204 }
196205
197206 /**
@@ -211,7 +220,8 @@ public ParallelCompositeUploadBlobWriteSessionConfig withPartNamingStrategy(
211220 executorSupplier ,
212221 bufferAllocationStrategy ,
213222 partNamingStrategy ,
214- partCleanupStrategy );
223+ partCleanupStrategy ,
224+ partMetadataFieldDecorator );
215225 }
216226
217227 /**
@@ -231,7 +241,29 @@ public ParallelCompositeUploadBlobWriteSessionConfig withPartCleanupStrategy(
231241 executorSupplier ,
232242 bufferAllocationStrategy ,
233243 partNamingStrategy ,
234- partCleanupStrategy );
244+ partCleanupStrategy ,
245+ partMetadataFieldDecorator );
246+ }
247+
248+ /**
249+ * Specify a Part Metadata Field decorator, this will manipulate the metadata associated with part
250+ * objects, the ultimate object metadata will remain unchanged.
251+ *
252+ * <p><i>Default: </i> {@link PartMetadataFieldDecorator#noOp()}
253+ *
254+ * @since 2.36.0 This new api is in preview and is subject to breaking changes.
255+ */
256+ @ BetaApi
257+ public ParallelCompositeUploadBlobWriteSessionConfig withPartMetadataFieldDecorator (
258+ PartMetadataFieldDecorator partMetadataFieldDecorator ) {
259+ checkNotNull (partMetadataFieldDecorator , "partMetadataFieldDecorator must be non null" );
260+ return new ParallelCompositeUploadBlobWriteSessionConfig (
261+ maxPartsPerCompose ,
262+ executorSupplier ,
263+ bufferAllocationStrategy ,
264+ partNamingStrategy ,
265+ partCleanupStrategy ,
266+ partMetadataFieldDecorator );
235267 }
236268
237269 @ BetaApi
@@ -241,15 +273,19 @@ static ParallelCompositeUploadBlobWriteSessionConfig withDefaults() {
241273 ExecutorSupplier .cachedPool (),
242274 BufferAllocationStrategy .simple (ByteSizeConstants ._16MiB ),
243275 PartNamingStrategy .noPrefix (),
244- PartCleanupStrategy .always ());
276+ PartCleanupStrategy .always (),
277+ PartMetadataFieldDecorator .noOp ());
245278 }
246279
247280 @ InternalApi
248281 @ Override
249282 WriterFactory createFactory (Clock clock ) throws IOException {
250283 Executor executor = executorSupplier .get ();
251284 BufferHandlePool bufferHandlePool = bufferAllocationStrategy .get ();
252- return new ParallelCompositeUploadWriterFactory (clock , executor , bufferHandlePool );
285+ PartMetadataFieldDecoratorInstance partMetadataFieldDecoratorInstance =
286+ partMetadataFieldDecorator .newInstance (clock );
287+ return new ParallelCompositeUploadWriterFactory (
288+ clock , executor , bufferHandlePool , partMetadataFieldDecoratorInstance );
253289 }
254290
255291 /**
@@ -277,6 +313,7 @@ private BufferAllocationStrategy() {}
277313 */
278314 @ BetaApi
279315 public static BufferAllocationStrategy simple (int capacity ) {
316+ checkArgument (capacity > 0 , "bufferCapacity must be > 0" );
280317 return new SimpleBufferAllocationStrategy (capacity );
281318 }
282319
@@ -291,6 +328,8 @@ public static BufferAllocationStrategy simple(int capacity) {
291328 */
292329 @ BetaApi
293330 public static BufferAllocationStrategy fixedPool (int bufferCount , int bufferCapacity ) {
331+ checkArgument (bufferCount > 0 , "bufferCount must be > 0" );
332+ checkArgument (bufferCapacity > 0 , "bufferCapacity must be > 0" );
294333 return new FixedPoolBufferAllocationStrategy (bufferCount , bufferCapacity );
295334 }
296335
@@ -361,6 +400,7 @@ public static ExecutorSupplier cachedPool() {
361400 */
362401 @ BetaApi
363402 public static ExecutorSupplier fixedPool (int poolSize ) {
403+ checkArgument (poolSize > 0 , "poolSize must be > 0" );
364404 return new FixedSupplier (poolSize );
365405 }
366406
@@ -631,6 +671,79 @@ protected String fmtFields(String randomKey, String ultimateObjectName, String p
631671 }
632672 }
633673
674+ /**
675+ * A Decorator which is used to manipulate metadata fields, specifically on the part objects
676+ * created in a Parallel Composite Upload
677+ *
678+ * @see #withPartMetadataFieldDecorator(PartMetadataFieldDecorator)
679+ * @since 2.36.0 This new api is in preview and is subject to breaking changes.
680+ */
681+ @ BetaApi
682+ @ Immutable
683+ public abstract static class PartMetadataFieldDecorator implements Serializable {
684+
685+ abstract PartMetadataFieldDecoratorInstance newInstance (Clock clock );
686+
687+ /**
688+ * A decorator that is used to manipulate the Custom Time Metadata field of part files. {@link
689+ * BlobInfo#getCustomTimeOffsetDateTime()}
690+ *
691+ * <p>When provided with a duration, a time in the future will be calculated for each part
692+ * object upon upload, this new value can be used in OLM rules to cleanup abandoned part files.
693+ *
694+ * <p>See [CustomTime OLM
695+ * documentation](https://cloud.google.com/storage/docs/lifecycle#dayssincecustomtime)
696+ *
697+ * @see #withPartMetadataFieldDecorator(PartMetadataFieldDecorator)
698+ * @since 2.36.0 This new api is in preview and is subject to breaking changes.
699+ */
700+ @ BetaApi
701+ public static PartMetadataFieldDecorator setCustomTimeInFuture (Duration timeInFuture ) {
702+ checkNotNull (timeInFuture , "timeInFuture must not be null" );
703+ return new CustomTimeInFuture (timeInFuture );
704+ }
705+
706+ @ BetaApi
707+ public static PartMetadataFieldDecorator noOp () {
708+ return NoOp .INSTANCE ;
709+ }
710+
711+ @ BetaApi
712+ private static final class CustomTimeInFuture extends PartMetadataFieldDecorator {
713+ private static final long serialVersionUID = -6213742554954751892L ;
714+ private final Duration duration ;
715+
716+ CustomTimeInFuture (Duration duration ) {
717+ this .duration = duration ;
718+ }
719+
720+ @ Override
721+ PartMetadataFieldDecoratorInstance newInstance (Clock clock ) {
722+ return builder -> {
723+ OffsetDateTime futureTime =
724+ OffsetDateTime .from (
725+ clock .instant ().plus (duration ).atZone (clock .getZone ()).toOffsetDateTime ());
726+ return builder .setCustomTimeOffsetDateTime (futureTime );
727+ };
728+ }
729+ }
730+
731+ private static final class NoOp extends PartMetadataFieldDecorator {
732+ private static final long serialVersionUID = -4569486383992999205L ;
733+ private static final NoOp INSTANCE = new NoOp ();
734+
735+ @ Override
736+ PartMetadataFieldDecoratorInstance newInstance (Clock clock ) {
737+ return builder -> builder ;
738+ }
739+
740+ /** prevent java serialization from using a new instance */
741+ private Object readResolve () {
742+ return INSTANCE ;
743+ }
744+ }
745+ }
746+
634747 /**
635748 * A cleanup strategy which will dictate what cleanup operations are performed automatically when
636749 * performing a parallel composite upload.
@@ -708,6 +821,8 @@ public static PartCleanupStrategy never() {
708821 }
709822 }
710823
824+ interface PartMetadataFieldDecoratorInstance extends UnaryOperator <BlobInfo .Builder > {}
825+
711826 private abstract static class Factory <T > implements Serializable {
712827 private static final long serialVersionUID = 271806144227661056L ;
713828
@@ -721,12 +836,17 @@ private class ParallelCompositeUploadWriterFactory implements WriterFactory {
721836 private final Clock clock ;
722837 private final Executor executor ;
723838 private final BufferHandlePool bufferHandlePool ;
839+ private final PartMetadataFieldDecoratorInstance partMetadataFieldDecoratorInstance ;
724840
725841 private ParallelCompositeUploadWriterFactory (
726- Clock clock , Executor executor , BufferHandlePool bufferHandlePool ) {
842+ Clock clock ,
843+ Executor executor ,
844+ BufferHandlePool bufferHandlePool ,
845+ PartMetadataFieldDecoratorInstance partMetadataFieldDecoratorInstance ) {
727846 this .clock = clock ;
728847 this .executor = executor ;
729848 this .bufferHandlePool = bufferHandlePool ;
849+ this .partMetadataFieldDecoratorInstance = partMetadataFieldDecoratorInstance ;
730850 }
731851
732852 @ Override
@@ -760,6 +880,7 @@ public ApiFuture<BufferedWritableByteChannel> openAsync() {
760880 partNamingStrategy ,
761881 partCleanupStrategy ,
762882 maxPartsPerCompose ,
883+ partMetadataFieldDecoratorInstance ,
763884 result ,
764885 storageInternal ,
765886 info ,
0 commit comments