Skip to content

Commit 43b8006

Browse files
authored
feat: Add Custom Part Metadata Decorator to ParallelCompositeUploadConfig (#2434)
1 parent 12c9db8 commit 43b8006

7 files changed

+241
-14
lines changed

‎google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadBlobWriteSessionConfig.java

Lines changed: 130 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,15 @@
4242
import java.nio.charset.StandardCharsets;
4343
import java.security.SecureRandom;
4444
import java.time.Clock;
45+
import java.time.Duration;
46+
import java.time.OffsetDateTime;
4547
import java.util.Base64;
4648
import java.util.Base64.Encoder;
4749
import java.util.concurrent.Executor;
4850
import java.util.concurrent.Executors;
4951
import java.util.concurrent.ThreadFactory;
5052
import java.util.concurrent.atomic.AtomicInteger;
53+
import java.util.function.UnaryOperator;
5154
import javax.annotation.concurrent.Immutable;
5255
import 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,

‎google-cloud-storage/src/main/java/com/google/cloud/storage/ParallelCompositeUploadWritableByteChannel.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.google.cloud.storage.BufferedWritableByteChannelSession.BufferedWritableByteChannel;
3232
import com.google.cloud.storage.MetadataField.PartRange;
3333
import com.google.cloud.storage.ParallelCompositeUploadBlobWriteSessionConfig.PartCleanupStrategy;
34+
import com.google.cloud.storage.ParallelCompositeUploadBlobWriteSessionConfig.PartMetadataFieldDecoratorInstance;
3435
import com.google.cloud.storage.ParallelCompositeUploadBlobWriteSessionConfig.PartNamingStrategy;
3536
import com.google.cloud.storage.Storage.ComposeRequest;
3637
import com.google.cloud.storage.UnifiedOpts.Crc32cMatch;
@@ -111,6 +112,7 @@ final class ParallelCompositeUploadWritableByteChannel implements BufferedWritab
111112
private final PartNamingStrategy partNamingStrategy;
112113
private final PartCleanupStrategy partCleanupStrategy;
113114
private final int maxElementsPerCompact;
115+
private final PartMetadataFieldDecoratorInstance partMetadataFieldDecorator;
114116
private final SettableApiFuture<BlobInfo> finalObject;
115117
private final StorageInternal storage;
116118
private final BlobInfo ultimateObject;
@@ -135,6 +137,7 @@ final class ParallelCompositeUploadWritableByteChannel implements BufferedWritab
135137
PartNamingStrategy partNamingStrategy,
136138
PartCleanupStrategy partCleanupStrategy,
137139
int maxElementsPerCompact,
140+
PartMetadataFieldDecoratorInstance partMetadataFieldDecorator,
138141
SettableApiFuture<BlobInfo> finalObject,
139142
StorageInternal storage,
140143
BlobInfo ultimateObject,
@@ -144,6 +147,7 @@ final class ParallelCompositeUploadWritableByteChannel implements BufferedWritab
144147
this.partNamingStrategy = partNamingStrategy;
145148
this.partCleanupStrategy = partCleanupStrategy;
146149
this.maxElementsPerCompact = maxElementsPerCompact;
150+
this.partMetadataFieldDecorator = partMetadataFieldDecorator;
147151
this.finalObject = finalObject;
148152
this.storage = storage;
149153
this.ultimateObject = ultimateObject;
@@ -427,6 +431,7 @@ private BlobInfo definePart(BlobInfo ultimateObject, PartRange partRange, long o
427431
PART_INDEX.appendTo(partRange, builder);
428432
OBJECT_OFFSET.appendTo(offset, builder);
429433
b.setMetadata(builder.build());
434+
b = partMetadataFieldDecorator.apply(b);
430435
return b.build();
431436
}
432437

‎google-cloud-storage/src/test/java/com/google/cloud/storage/ParallelCompositeUploadBlobWriteSessionConfigTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,13 @@
2121
import static com.google.common.truth.Truth.assertWithMessage;
2222

2323
import com.google.cloud.storage.MetadataField.PartRange;
24+
import com.google.cloud.storage.ParallelCompositeUploadBlobWriteSessionConfig.PartMetadataFieldDecorator;
2425
import com.google.cloud.storage.ParallelCompositeUploadBlobWriteSessionConfig.PartNamingStrategy;
2526
import com.google.common.truth.StringSubject;
27+
import java.time.Duration;
28+
import java.time.Instant;
29+
import java.time.OffsetDateTime;
30+
import java.time.ZoneId;
2631
import org.junit.Test;
2732

2833
public final class ParallelCompositeUploadBlobWriteSessionConfigTest {
@@ -87,6 +92,18 @@ public void partNameStrategy_objectNamePrefix() throws Exception {
8792
() -> assertThat(fmt).startsWith("a/b/obj"));
8893
}
8994

95+
@Test
96+
public void partMetadataFieldDecorator_customTime() {
97+
BlobInfo.Builder testBlob = BlobInfo.newBuilder("testBlob", "testBucket");
98+
Duration duration = Duration.ofSeconds(30);
99+
TestClock clock = TestClock.tickBy(Instant.EPOCH, Duration.ofSeconds(1));
100+
OffsetDateTime expected =
101+
OffsetDateTime.from(Instant.EPOCH.plus(duration).atZone(ZoneId.of("Z")));
102+
PartMetadataFieldDecorator.setCustomTimeInFuture(duration).newInstance(clock).apply(testBlob);
103+
104+
assertThat(expected).isEqualTo(testBlob.build().getCustomTimeOffsetDateTime());
105+
}
106+
90107
private static StringSubject assertField(String fmt, int idx) {
91108
String[] split = fmt.split(";");
92109
String s = split[idx];

0 commit comments

Comments
 (0)