/** * Logs the given MultiObjectDeleteException. * * @param multiObjectDeleteException The exception to log */ private void logMultiObjectDeleteException(MultiObjectDeleteException multiObjectDeleteException) { // Create and initialize a string buffer. The initialization is required here in order to avoid an InsufficientStringBufferDeclaration PMD violation. StringBuilder builder = new StringBuilder(128); builder.append(String.format("Error deleting multiple objects. Below are the list of objects which failed to delete.%n")); List<DeleteError> deleteErrors = multiObjectDeleteException.getErrors(); for (DeleteError deleteError : deleteErrors) { String key = deleteError.getKey(); String versionId = deleteError.getVersionId(); String code = deleteError.getCode(); String message = deleteError.getMessage(); builder .append(String.format("s3Key=\"%s\" s3VersionId=\"%s\" s3DeleteErrorCode=\"%s\" s3DeleteErrorMessage=\"%s\"%n", key, versionId, code, message)); } LOGGER.error(builder.toString()); }
/** * Deletes a list of keys/key versions from the specified S3 bucket. * * @param s3Client the S3 client * @param s3BucketName the S3 bucket name * @param keyVersions the list of S3 keys/key versions */ private void deleteKeyVersions(AmazonS3Client s3Client, String s3BucketName, List<DeleteObjectsRequest.KeyVersion> keyVersions) { // Create a request to delete multiple objects in the specified bucket. DeleteObjectsRequest multiObjectDeleteRequest = new DeleteObjectsRequest(s3BucketName); // The Multi-Object Delete request can contain a list of up to 1000 keys. for (int i = 0; i < keyVersions.size() / MAX_KEYS_PER_DELETE_REQUEST + 1; i++) { List<DeleteObjectsRequest.KeyVersion> keysSubList = keyVersions.subList(i * MAX_KEYS_PER_DELETE_REQUEST, Math.min(keyVersions.size(), (i + 1) * MAX_KEYS_PER_DELETE_REQUEST)); multiObjectDeleteRequest.setKeys(keysSubList); try { s3Operations.deleteObjects(multiObjectDeleteRequest, s3Client); } catch (MultiObjectDeleteException multiObjectDeleteException) { logMultiObjectDeleteException(multiObjectDeleteException); throw multiObjectDeleteException; } LOGGER.info("Successfully requested the deletion of the listed below keys/key versions from the S3 bucket. s3KeyCount={} s3BucketName=\"{}\"", keysSubList.size(), s3BucketName); for (DeleteObjectsRequest.KeyVersion keyVersion : keysSubList) { LOGGER.info("s3Key=\"{}\" s3VersionId=\"{}\"", keyVersion.getKey(), keyVersion.getVersion()); } } }
@Override public DeleteObjectsResult deleteObjects(DeleteObjectsRequest deleteObjectsRequest) { deleteObjectsRequest = beforeClientExecution(deleteObjectsRequest); Request<DeleteObjectsRequest> request = createRequest(deleteObjectsRequest.getBucketName(), null, deleteObjectsRequest, HttpMethodName.POST); request.addParameter("delete", null); if ( deleteObjectsRequest.getMfa() != null ) { populateRequestWithMfaDetails(request, deleteObjectsRequest.getMfa()); } populateRequesterPaysHeader(request, deleteObjectsRequest.isRequesterPays()); byte[] content = new MultiObjectDeleteXmlFactory().convertToXmlByteArray(deleteObjectsRequest); request.addHeader("Content-Length", String.valueOf(content.length)); request.addHeader("Content-Type", "application/xml"); request.setContent(new ByteArrayInputStream(content)); try { byte[] md5 = Md5Utils.computeMD5Hash(content); String md5Base64 = BinaryUtils.toBase64(md5); request.addHeader("Content-MD5", md5Base64); } catch ( Exception e ) { throw new SdkClientException("Couldn't compute md5 sum", e); } @SuppressWarnings("unchecked") ResponseHeaderHandlerChain<DeleteObjectsResponse> responseHandler = new ResponseHeaderHandlerChain<DeleteObjectsResponse>( new Unmarshallers.DeleteObjectsResultUnmarshaller(), new S3RequesterChargedHeaderHandler<DeleteObjectsResponse>()); DeleteObjectsResponse response = invoke(request, responseHandler, deleteObjectsRequest.getBucketName(), null); /* * If the result was only partially successful, throw an exception */ if ( !response.getErrors().isEmpty() ) { Map<String, String> headers = responseHandler.getResponseHeaders(); MultiObjectDeleteException ex = new MultiObjectDeleteException( response.getErrors(), response.getDeletedObjects()); ex.setStatusCode(200); ex.setRequestId(headers.get(Headers.REQUEST_ID)); ex.setExtendedRequestId(headers.get(Headers.EXTENDED_REQUEST_ID)); ex.setCloudFrontId(headers.get(Headers.CLOUD_FRONT_ID)); throw ex; } DeleteObjectsResult result = new DeleteObjectsResult(response.getDeletedObjects(), response.isRequesterCharged()); return result; }
@Test public void testDeleteDirectoryMultiObjectDeleteException() { // Create an S3 file transfer request parameters DTO to access S3 objects. S3FileTransferRequestParamsDto s3FileTransferRequestParamsDto = new S3FileTransferRequestParamsDto(); s3FileTransferRequestParamsDto.setS3BucketName(S3_BUCKET_NAME); s3FileTransferRequestParamsDto.setS3KeyPrefix(S3_KEY_PREFIX); // Create a retry policy. RetryPolicy retryPolicy = new RetryPolicy(PredefinedRetryPolicies.DEFAULT_RETRY_CONDITION, PredefinedRetryPolicies.DEFAULT_BACKOFF_STRATEGY, INTEGER_VALUE, true); // Create an S3 version summary. S3VersionSummary s3VersionSummary = new S3VersionSummary(); s3VersionSummary.setKey(S3_KEY); s3VersionSummary.setVersionId(S3_VERSION_ID); // Create a version listing. VersionListing versionListing = new VersionListing(); versionListing.setVersionSummaries(Arrays.asList(s3VersionSummary)); // Create a delete error. MultiObjectDeleteException.DeleteError deleteError = new MultiObjectDeleteException.DeleteError(); deleteError.setKey(S3_KEY); deleteError.setVersionId(S3_VERSION_ID); deleteError.setCode(ERROR_CODE); deleteError.setMessage(ERROR_MESSAGE); // Create a multi object delete exception. MultiObjectDeleteException multiObjectDeleteException = new MultiObjectDeleteException(Arrays.asList(deleteError), new ArrayList<>()); // Mock the external calls. when(retryPolicyFactory.getRetryPolicy()).thenReturn(retryPolicy); when(s3Operations.listVersions(any(ListVersionsRequest.class), any(AmazonS3Client.class))).thenReturn(versionListing); when(s3Operations.deleteObjects(any(DeleteObjectsRequest.class), any(AmazonS3Client.class))).thenThrow(multiObjectDeleteException); // Try to call the method under test. try { s3DaoImpl.deleteDirectory(s3FileTransferRequestParamsDto); } catch (IllegalStateException e) { assertEquals(String.format("Failed to delete keys/key versions with prefix \"%s\" from bucket \"%s\". " + "Reason: One or more objects could not be deleted (Service: null; Status Code: 0; Error Code: null; Request ID: null; S3 Extended Request ID: null)", S3_KEY_PREFIX, S3_BUCKET_NAME), e.getMessage()); } // Verify the external calls. verify(retryPolicyFactory, times(2)).getRetryPolicy(); verify(s3Operations).listVersions(any(ListVersionsRequest.class), any(AmazonS3Client.class)); verify(s3Operations).deleteObjects(any(DeleteObjectsRequest.class), any(AmazonS3Client.class)); verifyNoMoreInteractionsHelper(); }