Skip to content

Commit 4f0bd03

Browse files
feat: implement deleteSourceObjects for compose operation
Added deleteSourceObjects to Storage.ComposeRequest and Builder. Updated StorageImpl, GrpcStorageImpl and HttpStorageRpc to support the flag. Added unit test in StorageImplMockitoTest and integration test in ITObjectTest. Co-authored-by: nidhiii-27 <224584462+nidhiii-27@users.noreply.github.com>
1 parent 3d65c78 commit 4f0bd03

7 files changed

Lines changed: 92 additions & 1 deletion

File tree

java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,9 @@ public Blob compose(ComposeRequest composeRequest) {
645645
.forEach(builder::addSourceObjects);
646646
final Object target = codecs.blobInfo().encode(composeRequest.getTarget());
647647
builder.setDestination(target);
648+
if (composeRequest.isDeleteSourceObjects()) {
649+
builder.setDeleteSourceObjects(true);
650+
}
648651
ComposeObjectRequest req = opts.composeObjectsRequest().apply(builder).build();
649652
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
650653
return retrier.run(

java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3185,6 +3185,7 @@ class ComposeRequest implements Serializable {
31853185
private final List<SourceBlob> sourceBlobs;
31863186
private final BlobInfo target;
31873187
private final List<BlobTargetOption> targetOptions;
3188+
private final boolean deleteSourceObjects;
31883189

31893190
private transient Opts<ObjectTargetOpt> targetOpts;
31903191

@@ -3222,6 +3223,7 @@ public static class Builder {
32223223
private final Set<BlobTargetOption> targetOptions = new LinkedHashSet<>();
32233224
private BlobInfo target;
32243225
private Opts<ObjectTargetOpt> opts = Opts.empty();
3226+
private boolean deleteSourceObjects;
32253227

32263228
/** Add source blobs for compose operation. */
32273229
public Builder addSource(Iterable<String> blobs) {
@@ -3265,6 +3267,16 @@ public Builder setTargetOptions(Iterable<BlobTargetOption> options) {
32653267
return this;
32663268
}
32673269

3270+
/**
3271+
* Sets whether to delete source blobs after compose operation.
3272+
*
3273+
* @since 2.67.0
3274+
*/
3275+
public Builder setDeleteSourceObjects(boolean deleteSourceObjects) {
3276+
this.deleteSourceObjects = deleteSourceObjects;
3277+
return this;
3278+
}
3279+
32683280
/** Creates a {@code ComposeRequest} object. */
32693281
public ComposeRequest build() {
32703282
checkArgument(!sourceBlobs.isEmpty());
@@ -3280,6 +3292,7 @@ private ComposeRequest(Builder builder) {
32803292
// keep targetOptions for serialization even though we will read targetOpts
32813293
targetOptions = ImmutableList.copyOf(builder.targetOptions);
32823294
targetOpts = builder.opts.prepend(Opts.unwrap(targetOptions).resolveFrom(target));
3295+
deleteSourceObjects = builder.deleteSourceObjects;
32833296
}
32843297

32853298
/** Returns compose operation's source blobs. */
@@ -3297,6 +3310,11 @@ public List<BlobTargetOption> getTargetOptions() {
32973310
return targetOptions;
32983311
}
32993312

3313+
/** Returns whether to delete source blobs after compose operation. */
3314+
public boolean isDeleteSourceObjects() {
3315+
return deleteSourceObjects;
3316+
}
3317+
33003318
@InternalApi
33013319
Opts<ObjectTargetOpt> getTargetOpts() {
33023320
return targetOpts;

java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,10 @@ public Blob compose(final ComposeRequest composeRequest) {
651651
}
652652
Opts<ObjectTargetOpt> targetOpts = composeRequest.getTargetOpts();
653653
StorageObject targetPb = codecs.blobInfo().encode(composeRequest.getTarget());
654-
Map<StorageRpc.Option, ?> targetOptions = targetOpts.getRpcOptions();
654+
Map<StorageRpc.Option, Object> targetOptions = Maps.newHashMap(targetOpts.getRpcOptions());
655+
if (composeRequest.isDeleteSourceObjects()) {
656+
targetOptions.put(StorageRpc.Option.DELETE_SOURCE_OBJECTS, true);
657+
}
655658
ResultRetryAlgorithm<?> algorithm =
656659
retryAlgorithmManager.getForObjectsCompose(sources, targetPb, targetOptions);
657660
return run(

java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,9 @@ public StorageObject compose(
817817
sourceObjects.add(sourceObject);
818818
}
819819
request.setSourceObjects(sourceObjects);
820+
if (Option.DELETE_SOURCE_OBJECTS.getBoolean(targetOptions) != null) {
821+
request.setDeleteSourceObjects(Option.DELETE_SOURCE_OBJECTS.getBoolean(targetOptions));
822+
}
820823
Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_COMPOSE);
821824
Scope scope = tracer.withSpan(span);
822825
try {

java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ enum Option {
8282
INCLUDE_TRAILING_DELIMITER("includeTrailingDelimiter"),
8383
X_UPLOAD_CONTENT_LENGTH("x-upload-content-length"),
8484
OBJECT_FILTER("objectFilter"),
85+
DELETE_SOURCE_OBJECTS("deleteSourceObjects"),
8586
/**
8687
* An {@link com.google.common.collect.ImmutableMap ImmutableMap&lt;String, String>} of values
8788
* which will be set as additional headers on the request.

java-storage/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplMockitoTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,4 +1038,39 @@ private void verifyBucketNotification(Notification value) {
10381038
assertEquals(TOPIC, value.getTopic());
10391039
assertEquals(Arrays.asList(EVENT_TYPES), value.getEventTypes());
10401040
}
1041+
1042+
@Test
1043+
public void testComposeWithDeleteSourceObjects() {
1044+
String bucket = "b1";
1045+
String source1 = "s1";
1046+
String source2 = "s2";
1047+
String target = "t1";
1048+
BlobId targetId = BlobId.of(bucket, target);
1049+
BlobInfo targetInfo = BlobInfo.newBuilder(targetId).build();
1050+
Storage.ComposeRequest req =
1051+
Storage.ComposeRequest.newBuilder()
1052+
.addSource(source1, source2)
1053+
.setTarget(targetInfo)
1054+
.setDeleteSourceObjects(true)
1055+
.build();
1056+
1057+
StorageObject targetPb = Conversions.json().blobInfo().encode(targetInfo);
1058+
List<StorageObject> sourcePbs =
1059+
ImmutableList.of(
1060+
Conversions.json().blobInfo().encode(BlobInfo.newBuilder(bucket, source1).build()),
1061+
Conversions.json().blobInfo().encode(BlobInfo.newBuilder(bucket, source2).build()));
1062+
1063+
ArgumentCaptor<Map<StorageRpc.Option, Object>> optionsCaptor =
1064+
ArgumentCaptor.forClass(Map.class);
1065+
1066+
doReturn(targetPb)
1067+
.when(storageRpcMock)
1068+
.compose(Mockito.eq(sourcePbs), Mockito.eq(targetPb), optionsCaptor.capture());
1069+
1070+
initializeService();
1071+
storage.compose(req);
1072+
1073+
Map<StorageRpc.Option, Object> capturedOptions = optionsCaptor.getValue();
1074+
assertEquals(true, capturedOptions.get(StorageRpc.Option.DELETE_SOURCE_OBJECTS));
1075+
}
10411076
}

java-storage/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,34 @@ public void testComposeBlobWithContentType() {
812812
assertArrayEquals(composedBytes, readBytes);
813813
}
814814

815+
@Test
816+
public void testComposeBlobWithDeleteSourceObjects() {
817+
String baseName = generator.randomObjectName();
818+
String sourceBlobName1 = baseName + "-1";
819+
String sourceBlobName2 = baseName + "-2";
820+
BlobInfo sourceBlob1 = BlobInfo.newBuilder(bucket, sourceBlobName1).build();
821+
BlobInfo sourceBlob2 = BlobInfo.newBuilder(bucket, sourceBlobName2).build();
822+
storage.create(sourceBlob1, BLOB_BYTE_CONTENT);
823+
storage.create(sourceBlob2, BLOB_BYTE_CONTENT);
824+
825+
String targetBlobName = baseName + "-target";
826+
BlobInfo targetBlob = BlobInfo.newBuilder(bucket, targetBlobName).build();
827+
ComposeRequest req =
828+
ComposeRequest.newBuilder()
829+
.addSource(sourceBlobName1, sourceBlobName2)
830+
.setTarget(targetBlob)
831+
.setDeleteSourceObjects(true)
832+
.build();
833+
Blob remoteTargetBlob = storage.compose(req);
834+
assertNotNull(remoteTargetBlob);
835+
836+
assertNull(storage.get(bucket.getName(), sourceBlobName1));
837+
assertNull(storage.get(bucket.getName(), sourceBlobName2));
838+
839+
byte[] readBytes = storage.readAllBytes(bucket.getName(), targetBlobName);
840+
assertThat(readBytes.length).isEqualTo(BLOB_BYTE_CONTENT.length * 2);
841+
}
842+
815843
@Test
816844
public void testComposeBlobFail() {
817845
String baseName = generator.randomObjectName();

0 commit comments

Comments
 (0)