2828import software .amazon .awssdk .services .iam .model .CreateAccessKeyResponse ;
2929import software .amazon .awssdk .services .iam .model .IamException ;
3030import software .amazon .awssdk .services .s3 .S3AsyncClient ;
31+ import software .amazon .awssdk .services .s3 .model .BucketAlreadyExistsException ;
3132import software .amazon .awssdk .services .s3 .model .BucketInfo ;
3233import software .amazon .awssdk .services .s3 .model .BucketType ;
3334import software .amazon .awssdk .services .s3 .model .CopyObjectRequest ;
4748import software .amazon .awssdk .services .s3 .model .ListObjectsV2Request ;
4849import software .amazon .awssdk .services .s3 .model .LocationInfo ;
4950import software .amazon .awssdk .services .s3 .model .LocationType ;
51+ import software .amazon .awssdk .services .s3 .model .NoSuchBucketException ;
52+ import software .amazon .awssdk .services .s3 .model .NoSuchKeyException ;
5053import software .amazon .awssdk .services .s3 .model .ObjectIdentifier ;
5154import software .amazon .awssdk .services .s3 .model .PutObjectResponse ;
5255import software .amazon .awssdk .services .s3 .model .S3Object ;
@@ -181,7 +184,7 @@ public CompletableFuture<WaiterResponse<HeadBucketResponse>> deleteBucketAndObje
181184 * Lists the objects in an S3 bucket asynchronously.
182185 *
183186 * @param s3Client the S3 async client to use for the operation
184- * @param bucketName the name of the the S3 bucket containing the objects to list
187+ * @param bucketName the name of the S3 bucket containing the objects to list
185188 * @return a {@link CompletableFuture} that contains the list of object keys in the specified bucket
186189 */
187190 public CompletableFuture <List <String >> listObjectsAsync (S3AsyncClient s3Client , String bucketName ) {
@@ -218,14 +221,10 @@ public CompletableFuture<ResponseBytes<GetObjectResponse>> getObjectAsync(S3Asyn
218221 return s3Client .getObject (objectRequest , AsyncResponseTransformer .toBytes ())
219222 .exceptionally (exception -> {
220223 Throwable cause = exception .getCause ();
221- if (cause instanceof S3Exception ) {
224+ if (cause instanceof NoSuchKeyException ) {
222225 throw new CompletionException ("Failed to get the object. Reason: " + ((S3Exception ) cause ).awsErrorDetails ().errorMessage (), cause );
223226 }
224227 throw new CompletionException ("Failed to get the object" , exception );
225- })
226- .thenApply (response -> {
227- logger .info ("Successfully obtained bytes from an S3 object" );
228- return response ;
229228 });
230229 }
231230
@@ -318,8 +317,8 @@ public CompletableFuture<CreateBucketResponse> createDirectoryBucketAsync(S3Asyn
318317 .whenComplete ((response , exception ) -> {
319318 if (exception != null ) {
320319 Throwable cause = exception .getCause ();
321- if (cause instanceof S3Exception ) {
322- throw new CompletionException ("Error creating bucket: " + ((S3Exception ) cause ).awsErrorDetails ().errorMessage (), cause );
320+ if (cause instanceof BucketAlreadyExistsException ) {
321+ throw new CompletionException ("The bucket already exists : " + ((S3Exception ) cause ).awsErrorDetails ().errorMessage (), cause );
323322 }
324323 throw new CompletionException ("Unexpected error occurred while creating bucket" , exception );
325324 }
@@ -351,7 +350,12 @@ public CompletableFuture<WaiterResponse<HeadBucketResponse>> createBucketAsync(S
351350 })
352351 .whenComplete ((response , exception ) -> {
353352 if (exception != null ) {
354- throw new CompletionException ("Error creating bucket: " + bucketName , exception );
353+ Throwable cause = exception .getCause ();
354+ if (cause instanceof BucketAlreadyExistsException ) {
355+ throw new CompletionException ("The S3 bucket exists: " + cause .getMessage (), cause );
356+ } else {
357+ throw new CompletionException ("Failed to create access key: " + exception .getMessage (), exception );
358+ }
355359 }
356360 logger .info (bucketName + " is ready" );
357361 });
@@ -374,7 +378,12 @@ public CompletableFuture<PutObjectResponse> putObjectAsync(S3AsyncClient s3Clien
374378 return s3Client .putObject (objectRequest , AsyncRequestBody .fromString (text ))
375379 .whenComplete ((response , exception ) -> {
376380 if (exception != null ) {
377- throw new CompletionException ("Failed to upload file" , exception );
381+ Throwable cause = exception .getCause ();
382+ if (cause instanceof NoSuchBucketException ) {
383+ throw new CompletionException ("The S3 bucket does not exist: " + cause .getMessage (), cause );
384+ } else {
385+ throw new CompletionException ("Failed to create access key: " + exception .getMessage (), exception );
386+ }
378387 }
379388 });
380389 }
@@ -396,15 +405,13 @@ public CompletableFuture<CreateAccessKeyResponse> createAccessKeyAsync(String us
396405 logger .info ("Access Key Created." );
397406 } else {
398407 if (exception == null ) {
399- throw new CompletionException ( "An unknown error occurred while creating access key." , null );
400- }
401-
402- Throwable cause = exception . getCause ();
403- if ( cause instanceof IamException ) {
404- throw new CompletionException ( "IAM error while creating access key: " + cause . getMessage (), cause );
408+ Throwable cause = exception . getCause ( );
409+ if ( cause instanceof IamException ) {
410+ throw new CompletionException ( "IAM error while creating access key: " + cause . getMessage (), cause );
411+ } else {
412+ throw new CompletionException ( "Failed to create access key: " + exception . getMessage (), exception );
413+ }
405414 }
406-
407- throw new CompletionException ("Failed to create access key: " + exception .getMessage (), exception );
408415 }
409416 });
410417 }
@@ -422,7 +429,6 @@ public CompletableFuture<String> selectAvailabilityZoneIdAsync() {
422429 return getEc2AsyncClient ().describeAvailabilityZones (zonesRequest )
423430 .thenCompose (response -> {
424431 List <AvailabilityZone > zonesList = response .availabilityZones ();
425-
426432 if (zonesList .isEmpty ()) {
427433 logger .info ("No availability zones found." );
428434 return CompletableFuture .completedFuture (null ); // Return null if no zones are found
@@ -458,9 +464,9 @@ public CompletableFuture<String> selectAvailabilityZoneIdAsync() {
458464 /**
459465 * Prompts the user to select an Availability Zone from the given list.
460466 *
461- * @param zonesList the list of availability zones
467+ * @param zonesList the list of Availability Zones
462468 * @param zoneIds the list of zone IDs
463- * @return the selected AvailabilityZone
469+ * @return the selected Availability Zone
464470 */
465471 private static AvailabilityZone promptUserForZoneSelection (List <AvailabilityZone > zonesList , List <String > zoneIds ) {
466472 Scanner scanner = new Scanner (System .in );
@@ -486,14 +492,13 @@ private static AvailabilityZone promptUserForZoneSelection(List<AvailabilityZone
486492 }
487493
488494 /**
489- * Asynchronously sets up an AWS VPC, including creating a VPC, waiting for it to be available,
490- * retrieving its associated route table, and creating a VPC endpoint for S3 Express .
495+ * Asynchronously sets up a new VPC, including creating the VPC, finding the associated route table, and
496+ * creating a VPC endpoint for the S3 service .
491497 *
492- * @return A {@link CompletableFuture} that completes when the VPC setup is finished.
493- * If an error occurs, a {@link CompletionException} is thrown.
494- * @throws CompletionException if an EC2-related error occurs or if required resources are missing.
498+ * @return a {@link CompletableFuture} that, when completed, contains a AbstractMap with the
499+ * VPC ID and VPC endpoint ID.
495500 */
496- public CompletableFuture <Void > setupVPCAsync () {
501+ public CompletableFuture <AbstractMap . SimpleEntry < String , String > > setupVPCAsync () {
497502 String cidr = "10.0.0.0/16" ;
498503 CreateVpcRequest vpcRequest = CreateVpcRequest .builder ()
499504 .cidrBlock (cidr )
@@ -502,8 +507,9 @@ public CompletableFuture<Void> setupVPCAsync() {
502507 return getEc2AsyncClient ().createVpc (vpcRequest )
503508 .thenCompose (vpcResponse -> {
504509 String vpcId = vpcResponse .vpc ().vpcId ();
510+ logger .info ("VPC Created: {}" , vpcId );
505511
506- Ec2AsyncWaiter waiter = ec2AsyncClient .waiter ();
512+ Ec2AsyncWaiter waiter = getEc2AsyncClient () .waiter ();
507513 DescribeVpcsRequest request = DescribeVpcsRequest .builder ()
508514 .vpcIds (vpcId )
509515 .build ();
@@ -521,18 +527,20 @@ public CompletableFuture<Void> setupVPCAsync() {
521527 .filters (filter )
522528 .build ();
523529
524- return ec2AsyncClient .describeRouteTables (describeRouteTablesRequest )
530+ return getEc2AsyncClient () .describeRouteTables (describeRouteTablesRequest )
525531 .thenApply (routeTablesResponse -> {
526532 if (routeTablesResponse .routeTables ().isEmpty ()) {
527- throw new CompletionException ("No route tables found for VPC." , null );
533+ throw new CompletionException ("No route tables found for VPC: " + vpcId , null );
528534 }
529- return new AbstractMap .SimpleEntry <>(vpcId , routeTablesResponse .routeTables ().get (0 ).routeTableId ());
535+ String routeTableId = routeTablesResponse .routeTables ().get (0 ).routeTableId ();
536+ logger .info ("Route table found: {}" , routeTableId );
537+ return new AbstractMap .SimpleEntry <>(vpcId , routeTableId );
530538 });
531539 })
532540 .thenCompose (vpcAndRouteTable -> {
533541 String vpcId = vpcAndRouteTable .getKey ();
534542 String routeTableId = vpcAndRouteTable .getValue ();
535- Region region = ec2AsyncClient .serviceClientConfiguration ().region ();
543+ Region region = getEc2AsyncClient () .serviceClientConfiguration ().region ();
536544 String serviceName = String .format ("com.amazonaws.%s.s3express" , region .id ());
537545
538546 CreateVpcEndpointRequest endpointRequest = CreateVpcEndpointRequest .builder ()
@@ -541,30 +549,24 @@ public CompletableFuture<Void> setupVPCAsync() {
541549 .serviceName (serviceName )
542550 .build ();
543551
544- return ec2AsyncClient .createVpcEndpoint (endpointRequest )
552+ return getEc2AsyncClient () .createVpcEndpoint (endpointRequest )
545553 .thenApply (vpcEndpointResponse -> {
546554 String vpcEndpointId = vpcEndpointResponse .vpcEndpoint ().vpcEndpointId ();
555+ logger .info ("VPC Endpoint created: {}" , vpcEndpointId );
547556 return new AbstractMap .SimpleEntry <>(vpcId , vpcEndpointId );
548557 });
549558 })
550- .whenComplete ((result , exception ) -> {
551- if (result != null ) {
552- logger .info ("Created VPC: {}" , result .getKey ());
553- logger .info ("Created VPC Endpoint: {}" , result .getValue ());
554- } else {
555- if (exception == null ) {
556- throw new CompletionException ("An unknown error occurred during VPC setup." , null );
557- }
558-
559- Throwable cause = exception .getCause ();
560- if (cause instanceof Ec2Exception ) {
561- throw new CompletionException ("EC2 error during VPC setup: " + cause .getMessage (), cause );
562- }
563-
564- throw new CompletionException ("VPC setup failed: " + exception .getMessage (), exception );
559+ .exceptionally (exception -> {
560+ Throwable cause = exception .getCause () != null ? exception .getCause () : exception ;
561+ if (cause instanceof Ec2Exception ) {
562+ logger .error ("EC2 error during VPC setup: {}" , cause .getMessage (), cause );
563+ throw new CompletionException ("EC2 error during VPC setup: " + cause .getMessage (), cause );
565564 }
566- })
567- .thenAccept (v -> {});
565+
566+ logger .error ("VPC setup failed: {}" , cause .getMessage (), cause );
567+ throw new CompletionException ("VPC setup failed: " + cause .getMessage (), cause );
568+ });
568569 }
570+
569571}
570572// snippet-end:[s3.java2.directories.actions.main]
0 commit comments