4848import java .util .List ;
4949import java .util .Map ;
5050import java .util .Set ;
51+ import java .util .stream .Collectors ;
5152
5253/**
5354 * A wrapper around <a href="https://github.com/googleapis/java-storage">Google cloud storage
@@ -89,7 +90,7 @@ private static Storage createStorage(String projectId) {
8990 return StorageOptions .newBuilder ().build ().getService ();
9091 }
9192
92- WritableByteChannel create (final StorageResourceId resourceId , final CreateOptions options )
93+ WritableByteChannel create (final StorageResourceId resourceId , final CreateFileOptions options )
9394 throws IOException {
9495 LOG .trace ("create({})" , resourceId );
9596
@@ -402,6 +403,47 @@ void createEmptyObject(StorageResourceId resourceId, CreateObjectOptions options
402403 }
403404 }
404405
406+
407+ public GoogleCloudStorageItemInfo composeObjects (
408+ List <StorageResourceId > sources , StorageResourceId destination , CreateObjectOptions options )
409+ throws IOException {
410+ LOG .trace ("composeObjects({}, {}, {})" , sources , destination , options );
411+ for (StorageResourceId inputId : sources ) {
412+ if (!destination .getBucketName ().equals (inputId .getBucketName ())) {
413+ throw new IOException (
414+ String .format (
415+ "Bucket doesn't match for source '%s' and destination '%s'!" ,
416+ inputId , destination ));
417+ }
418+ }
419+ Storage .ComposeRequest request =
420+ Storage .ComposeRequest .newBuilder ()
421+ .addSource (
422+ sources .stream ().map (StorageResourceId ::getObjectName ).collect (Collectors .toList ()))
423+ .setTarget (
424+ BlobInfo .newBuilder (destination .getBucketName (), destination .getObjectName ())
425+ .setContentType (options .getContentType ())
426+ .setContentEncoding (options .getContentEncoding ())
427+ .setMetadata (encodeMetadata (options .getMetadata ()))
428+ .build ())
429+ .setTargetOptions (
430+ Storage .BlobTargetOption .generationMatch (
431+ destination .hasGenerationId ()
432+ ? destination .getGenerationId ()
433+ : getWriteGeneration (destination , true )))
434+ .build ();
435+
436+ Blob composedBlob ;
437+ try {
438+ composedBlob = storage .compose (request );
439+ } catch (StorageException e ) {
440+ throw new IOException (e );
441+ }
442+ GoogleCloudStorageItemInfo compositeInfo = createItemInfoForBlob (destination , composedBlob );
443+ LOG .trace ("composeObjects() done, returning: {}" , compositeInfo );
444+ return compositeInfo ;
445+ }
446+
405447 /**
406448 * Helper to check whether an empty object already exists with the expected metadata specified in
407449 * {@code options}, to be used to determine whether it's safe to ignore an exception that was
@@ -450,6 +492,7 @@ private boolean canIgnoreExceptionForEmptyObject(
450492 return true ;
451493 }
452494 }
495+
453496 return false ;
454497 }
455498
@@ -472,18 +515,14 @@ private void createEmptyObjectInternal(
472515 blobTargetOptions .add (Storage .BlobTargetOption .doesNotExist ());
473516 }
474517
475- try {
476- // TODO: Set encryption key and related properties
477- storage .create (
478- BlobInfo .newBuilder (BlobId .of (resourceId .getBucketName (), resourceId .getObjectName ()))
479- .setMetadata (rewrittenMetadata )
480- .setContentEncoding (createObjectOptions .getContentEncoding ())
481- .setContentType (createObjectOptions .getContentType ())
482- .build (),
483- blobTargetOptions .toArray (new Storage .BlobTargetOption [0 ]));
484- } catch (StorageException e ) {
485- throw new IOException (String .format ("Creating empty object %s failed." , resourceId ), e );
486- }
518+ // TODO: Set encryption key and related properties
519+ storage .create (
520+ BlobInfo .newBuilder (BlobId .of (resourceId .getBucketName (), resourceId .getObjectName ()))
521+ .setMetadata (rewrittenMetadata )
522+ .setContentEncoding (createObjectOptions .getContentEncoding ())
523+ .setContentType (createObjectOptions .getContentType ())
524+ .build (),
525+ blobTargetOptions .toArray (new Storage .BlobTargetOption [0 ]));
487526 }
488527
489528 private static Map <String , String > encodeMetadata (Map <String , byte []> metadata ) {
@@ -871,6 +910,23 @@ private static GoogleCloudStorageItemInfo getGoogleCloudStorageItemInfo(
871910 return storageItemInfo ;
872911 }
873912
913+ List <GoogleCloudStorageItemInfo > getItemInfos (List <StorageResourceId > resourceIds )
914+ throws IOException {
915+ LOG .trace ("getItemInfos({})" , resourceIds );
916+
917+ if (resourceIds .isEmpty ()) {
918+ return new ArrayList <>();
919+ }
920+
921+ List <GoogleCloudStorageItemInfo > result = new ArrayList <>(resourceIds .size ());
922+ for (StorageResourceId resourceId : resourceIds ) {
923+ // TODO: Do this concurrently
924+ result .add (getItemInfo (resourceId ));
925+ }
926+
927+ return result ;
928+ }
929+
874930 // Helper class to capture the results of list operation.
875931 private class ListOperationResult {
876932 private final Map <String , Blob > prefixes = new HashMap <>();
0 commit comments