Skip to content

Commit 2cecd0c

Browse files
nidhiii-27google-labs-jules[bot]cloud-java-botDhriti07
authored
feat(storage): Implement deleteSourceObjects for Compose Operation (#12873)
Implemented the `deleteSourceObjects` feature for the `compose` operation in the Google Cloud Storage Java client. Changes: 1. **Storage.java**: Added `deleteSourceObjects` field to `ComposeRequest` and its `Builder`. 2. **StorageRpc.java**: Added `DELETE_SOURCE_OBJECTS` option. 3. **StorageImpl.java**: Updated `compose` to propagate the new option. 4. **GrpcStorageImpl.java**: Mapped the option to the gRPC `ComposeObjectRequest` proto. 5. **HttpStorageRpc.java**: Mapped the option to the JSON `ComposeRequest` model. 6. **StorageImplMockitoTest.java**: Added unit test for option propagation. 7. **ITObjectTest.java**: Added integration test for the full feature workflow. --- *PR created automatically by Jules for task [10602500430278326901](https://jules.google.com/task/10602500430278326901) started by @nidhiii-27* --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: nidhiii-27 <224584462+nidhiii-27@users.noreply.github.com> Co-authored-by: cloud-java-bot <cloud-java-bot@google.com> Co-authored-by: Dhriti07 <56169283+Dhriti07@users.noreply.github.com>
1 parent 0bca75c commit 2cecd0c

7 files changed

Lines changed: 88 additions & 1 deletion

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,7 @@ public Blob compose(ComposeRequest composeRequest) {
645645
.forEach(builder::addSourceObjects);
646646
final Object target = codecs.blobInfo().encode(composeRequest.getTarget());
647647
builder.setDestination(target);
648+
builder.setDeleteSourceObjects(composeRequest.isDeleteSourceObjects());
648649
ComposeObjectRequest req = opts.composeObjectsRequest().apply(builder).build();
649650
GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext());
650651
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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,7 @@ public StorageObject compose(
817817
sourceObjects.add(sourceObject);
818818
}
819819
request.setSourceObjects(sourceObjects);
820+
request.setDeleteSourceObjects(Option.DELETE_SOURCE_OBJECTS.getBoolean(targetOptions));
820821
Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_COMPOSE);
821822
Scope scope = tracer.withSpan(span);
822823
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)