From a9871d344ae3f0ec7b04b208776c2bffe089d4c2 Mon Sep 17 00:00:00 2001 From: Luo Date: Sun, 1 Jan 2023 17:16:41 +0800 Subject: [PATCH 1/3] implement delete objects --- .../s3/core/service/DeleteObjectsService.java | 56 +++++++++++++ .../service/DeleteObjectsServiceTest.java | 49 +++++++++++ .../s3/datatypes/ObjectIdentifier.java | 27 ++++++ .../request/DeleteObjectsRequest.java | 28 +++++++ .../s3/datatypes/response/DeleteResult.java | 83 +++++++++++++++++++ .../request/DeleteObjectsRequestTest.java | 40 +++++++++ .../datatypes/response/DeleteResultTest.java | 20 +++++ .../rest/handler/DeleteObjectsController.java | 47 +++++++++++ 8 files changed, 350 insertions(+) create mode 100644 local-s3-core/src/main/java/com/robothy/s3/core/service/DeleteObjectsService.java create mode 100644 local-s3-core/src/test/java/com/robothy/s3/core/service/DeleteObjectsServiceTest.java create mode 100644 local-s3-datatypes/src/main/java/com/robothy/s3/datatypes/ObjectIdentifier.java create mode 100644 local-s3-datatypes/src/main/java/com/robothy/s3/datatypes/request/DeleteObjectsRequest.java create mode 100644 local-s3-datatypes/src/main/java/com/robothy/s3/datatypes/response/DeleteResult.java create mode 100644 local-s3-datatypes/src/test/java/com/robothy/s3/datatypes/request/DeleteObjectsRequestTest.java create mode 100644 local-s3-datatypes/src/test/java/com/robothy/s3/datatypes/response/DeleteResultTest.java create mode 100644 local-s3-rest/src/main/java/com/robothy/s3/rest/handler/DeleteObjectsController.java diff --git a/local-s3-core/src/main/java/com/robothy/s3/core/service/DeleteObjectsService.java b/local-s3-core/src/main/java/com/robothy/s3/core/service/DeleteObjectsService.java new file mode 100644 index 0000000..8adbe03 --- /dev/null +++ b/local-s3-core/src/main/java/com/robothy/s3/core/service/DeleteObjectsService.java @@ -0,0 +1,56 @@ +package com.robothy.s3.core.service; + +import com.robothy.s3.core.exception.LocalS3Exception; +import com.robothy.s3.core.model.answers.DeleteObjectAns; +import com.robothy.s3.datatypes.ObjectIdentifier; +import com.robothy.s3.datatypes.request.DeleteObjectsRequest; +import com.robothy.s3.datatypes.response.DeleteResult; +import com.robothy.s3.datatypes.response.S3Error; +import java.util.ArrayList; +import java.util.List; + +public interface DeleteObjectsService extends DeleteObjectService { + + /** + * Delete objects from a specified bucket. + * + * @param bucketName bucket name. + * @param request delete objects request. + * @return delete results. + */ + default List deleteObjects(String bucketName, DeleteObjectsRequest request) { + + List results = new ArrayList<>(request.getObjects().size()); + for (ObjectIdentifier id : request.getObjects()) { + String key = id.getKey(); + String versionId = id.getVersionId().orElse(null); + try { + DeleteObjectAns deleteObjectAns = deleteObject(bucketName, key, versionId); + if (request.isQuiet()) { + continue; + } + + DeleteResult.Deleted deleted = new DeleteResult.Deleted(); + deleted.setKey(key); + deleted.setVersionId(versionId); + if (deleteObjectAns.isDeleteMarker()) { + deleted.setDeleteMarker(true); + deleted.setDeleteMarkerVersionId(deleteObjectAns.getVersionId()); + } + results.add(deleted); + } catch (Throwable e) { + S3Error.S3ErrorBuilder builder = S3Error.builder() + .bucketName(bucketName) + .message(e.getMessage()) + .key(key) + .versionId(versionId); + if (e instanceof LocalS3Exception) { + builder.code(((LocalS3Exception) e).getS3ErrorCode().code()); + } + results.add(builder.build()); + } + } + return results; + } + +} diff --git a/local-s3-core/src/test/java/com/robothy/s3/core/service/DeleteObjectsServiceTest.java b/local-s3-core/src/test/java/com/robothy/s3/core/service/DeleteObjectsServiceTest.java new file mode 100644 index 0000000..8633b18 --- /dev/null +++ b/local-s3-core/src/test/java/com/robothy/s3/core/service/DeleteObjectsServiceTest.java @@ -0,0 +1,49 @@ +package com.robothy.s3.core.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; +import com.robothy.s3.core.exception.S3ErrorCode; +import com.robothy.s3.core.model.internal.ObjectMetadata; +import com.robothy.s3.datatypes.ObjectIdentifier; +import com.robothy.s3.datatypes.request.DeleteObjectsRequest; +import com.robothy.s3.datatypes.response.DeleteResult; +import com.robothy.s3.datatypes.response.S3Error; +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class DeleteObjectsServiceTest extends LocalS3ServiceTestBase { + + @MethodSource("localS3Services") + @ParameterizedTest + void testDeleteObjects(BucketService bucketService, ObjectService objectService) { + String bucketName = "my-bucket"; + bucketService.createBucket(bucketName); + + List objectsToDelete = List.of( + new ObjectIdentifier("a.txt", "123"), + new ObjectIdentifier("b.txt", null) + ); + DeleteObjectsRequest request1 = new DeleteObjectsRequest(objectsToDelete, false); + + List results = objectService.deleteObjects(bucketName, request1); + + assertInstanceOf(S3Error.class, results.get(0)); + S3Error s3Error = (S3Error) results.get(0); + assertEquals("a.txt", s3Error.getKey()); + assertEquals("123", s3Error.getVersionId()); + assertEquals(S3ErrorCode.NoSuchKey.code(), s3Error.getCode()); + + assertInstanceOf(DeleteResult.Deleted.class, results.get(1)); + DeleteResult.Deleted deleted = (DeleteResult.Deleted) results.get(1); + assertTrue(deleted.isDeleteMarker()); + assertEquals(ObjectMetadata.NULL_VERSION, deleted.getDeleteMarkerVersionId()); + + DeleteObjectsRequest request2 = new DeleteObjectsRequest(objectsToDelete, true); + List results2 = objectService.deleteObjects(bucketName, request2); + assertEquals(1, results2.size()); + assertInstanceOf(S3Error.class, results2.get(0)); + } + +} \ No newline at end of file diff --git a/local-s3-datatypes/src/main/java/com/robothy/s3/datatypes/ObjectIdentifier.java b/local-s3-datatypes/src/main/java/com/robothy/s3/datatypes/ObjectIdentifier.java new file mode 100644 index 0000000..c24531c --- /dev/null +++ b/local-s3-datatypes/src/main/java/com/robothy/s3/datatypes/ObjectIdentifier.java @@ -0,0 +1,27 @@ +package com.robothy.s3.datatypes; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import java.util.Optional; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class ObjectIdentifier { + + @JacksonXmlProperty(localName = "Key") + private String key; + + @JacksonXmlProperty(localName = "VersionId") + private String versionId; + + public Optional getVersionId() { + return Optional.ofNullable(versionId); + } + +} diff --git a/local-s3-datatypes/src/main/java/com/robothy/s3/datatypes/request/DeleteObjectsRequest.java b/local-s3-datatypes/src/main/java/com/robothy/s3/datatypes/request/DeleteObjectsRequest.java new file mode 100644 index 0000000..82c8a20 --- /dev/null +++ b/local-s3-datatypes/src/main/java/com/robothy/s3/datatypes/request/DeleteObjectsRequest.java @@ -0,0 +1,28 @@ +package com.robothy.s3.datatypes.request; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import com.robothy.s3.datatypes.ObjectIdentifier; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@JacksonXmlRootElement(localName = "Delete") +public class DeleteObjectsRequest { + + @JacksonXmlElementWrapper(useWrapping = false) + @JacksonXmlProperty(localName = "Object") + private List objects = new ArrayList<>(); + + @JacksonXmlProperty(localName = "Quiet") + private boolean quiet; + +} diff --git a/local-s3-datatypes/src/main/java/com/robothy/s3/datatypes/response/DeleteResult.java b/local-s3-datatypes/src/main/java/com/robothy/s3/datatypes/response/DeleteResult.java new file mode 100644 index 0000000..c05a047 --- /dev/null +++ b/local-s3-datatypes/src/main/java/com/robothy/s3/datatypes/response/DeleteResult.java @@ -0,0 +1,83 @@ +package com.robothy.s3.datatypes.response; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Objects; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.SneakyThrows; + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@JacksonXmlRootElement(localName = "DeleteResult") +@JsonSerialize(using = DeleteResult.DeleteResultSerializer.class) +public class DeleteResult { + + @JacksonXmlElementWrapper(useWrapping = false) + private List deletedList; + + @Setter + @Getter + @JacksonXmlRootElement(localName = "Deleted") + public static class Deleted { + + @JacksonXmlProperty(localName = "DeleteMarker") + private boolean deleteMarker; + + @JacksonXmlProperty(localName = "DeleteMarkerVersionId") + private String deleteMarkerVersionId; + + @JacksonXmlProperty(localName = "Key") + private String key; + + @JacksonXmlProperty(localName = "VersionId") + private String versionId; + + } + + static class DeleteResultSerializer extends StdSerializer { + + DeleteResultSerializer() { + this(null); + } + + protected DeleteResultSerializer(Class t) { + super(t); + } + + @SneakyThrows + @Override + public void serialize(DeleteResult deleteResult, JsonGenerator gen, SerializerProvider provider) throws IOException { + + if (gen instanceof ToXmlGenerator) { + gen.writeStartObject(); + Field deletedListField = DeleteResult.class.getDeclaredField("deletedList"); + deletedListField.setAccessible(true); + for (Object item : (List)deletedListField.get(deleteResult)) { + JacksonXmlRootElement jacksonXmlRootElement = item.getClass().getDeclaredAnnotation(JacksonXmlRootElement.class); + Objects.requireNonNull(jacksonXmlRootElement, "Must add @JacksonXmlRootElement to " + item.getClass()); + gen.writeFieldName(jacksonXmlRootElement.localName()); + gen.writeObject(item); + } + + gen.writeEndObject(); + } + + } + + } + +} diff --git a/local-s3-datatypes/src/test/java/com/robothy/s3/datatypes/request/DeleteObjectsRequestTest.java b/local-s3-datatypes/src/test/java/com/robothy/s3/datatypes/request/DeleteObjectsRequestTest.java new file mode 100644 index 0000000..13fd47d --- /dev/null +++ b/local-s3-datatypes/src/test/java/com/robothy/s3/datatypes/request/DeleteObjectsRequestTest.java @@ -0,0 +1,40 @@ +package com.robothy.s3.datatypes.request; + +import static org.junit.jupiter.api.Assertions.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.robothy.s3.datatypes.ObjectIdentifier; +import org.junit.jupiter.api.Test; + +class DeleteObjectsRequestTest { + + @Test + void testDeserializeDeleteObjectsRequest() throws JsonProcessingException { + + String xml = "\n" + + " \n" + + " a.txt\n" + + " null\n" + + " \n" + + " \n" + + " b.txt\n" + + " \n" + + " true\n" + + ""; + + XmlMapper xmlMapper = new XmlMapper(); + DeleteObjectsRequest request = xmlMapper.readValue(xml, DeleteObjectsRequest.class); + assertEquals(2, request.getObjects().size()); + ObjectIdentifier objectIdentifier1 = request.getObjects().get(0); + assertEquals("a.txt", objectIdentifier1.getKey()); + assertTrue(objectIdentifier1.getVersionId().isPresent()); + assertEquals("null", objectIdentifier1.getVersionId().get()); + + ObjectIdentifier objectIdentifier2 = request.getObjects().get(1); + assertEquals("b.txt", objectIdentifier2.getKey()); + assertTrue(objectIdentifier2.getVersionId().isEmpty()); + + assertTrue(request.isQuiet()); + } + +} \ No newline at end of file diff --git a/local-s3-datatypes/src/test/java/com/robothy/s3/datatypes/response/DeleteResultTest.java b/local-s3-datatypes/src/test/java/com/robothy/s3/datatypes/response/DeleteResultTest.java new file mode 100644 index 0000000..a06d09a --- /dev/null +++ b/local-s3-datatypes/src/test/java/com/robothy/s3/datatypes/response/DeleteResultTest.java @@ -0,0 +1,20 @@ +package com.robothy.s3.datatypes.response; + +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class DeleteResultTest { + + @Test + void testSerialization() { + DeleteResult deleteResult = new DeleteResult(); + deleteResult.setDeletedList(List.of(new S3Error(), new DeleteResult.Deleted())); + + XmlMapper xmlMapper = new XmlMapper(); + Assertions.assertDoesNotThrow(() -> xmlMapper.writerWithDefaultPrettyPrinter() + .writeValueAsString(deleteResult)); + } + +} \ No newline at end of file diff --git a/local-s3-rest/src/main/java/com/robothy/s3/rest/handler/DeleteObjectsController.java b/local-s3-rest/src/main/java/com/robothy/s3/rest/handler/DeleteObjectsController.java new file mode 100644 index 0000000..ce93d32 --- /dev/null +++ b/local-s3-rest/src/main/java/com/robothy/s3/rest/handler/DeleteObjectsController.java @@ -0,0 +1,47 @@ +package com.robothy.s3.rest.handler; + +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.robothy.netty.http.HttpRequest; +import com.robothy.netty.http.HttpRequestHandler; +import com.robothy.netty.http.HttpResponse; +import com.robothy.s3.core.service.DeleteObjectsService; +import com.robothy.s3.core.service.ObjectService; +import com.robothy.s3.datatypes.request.DeleteObjectsRequest; +import com.robothy.s3.datatypes.response.DeleteResult; +import com.robothy.s3.rest.assertions.RequestAssertions; +import com.robothy.s3.rest.service.ServiceFactory; +import com.robothy.s3.rest.utils.RequestUtils; +import com.robothy.s3.rest.utils.ResponseUtils; +import io.netty.handler.codec.http.HttpResponseStatus; +import java.io.InputStream; +import java.util.List; + +class DeleteObjectsController implements HttpRequestHandler { + + private final DeleteObjectsService deleteObjectsService; + + private final XmlMapper xmlMapper; + + DeleteObjectsController(ServiceFactory serviceFactory) { + this.deleteObjectsService = serviceFactory.getInstance(ObjectService.class); + this.xmlMapper = serviceFactory.getInstance(XmlMapper.class); + } + + @Override + public void handle(HttpRequest request, HttpResponse response) throws Exception { + String bucketName = RequestAssertions.assertBucketNameProvided(request); + + try (InputStream decodedBody = RequestUtils.getBody(request).getDecodedBody()) { + DeleteObjectsRequest deleteObjectsRequest = + xmlMapper.readValue(decodedBody, DeleteObjectsRequest.class); + List deletedList = this.deleteObjectsService.deleteObjects(bucketName, deleteObjectsRequest); + DeleteResult deleteResult = new DeleteResult(deletedList); + String xml = xmlMapper.writeValueAsString(deleteResult); + response.status(HttpResponseStatus.OK) + .write(xml); + ResponseUtils.addCommonHeaders(response); + } + + } + +} From 3dfbb7c9de20dd175e2a8a8e9fd16751cd990d01 Mon Sep 17 00:00:00 2001 From: Luo Date: Sun, 1 Jan 2023 17:17:33 +0800 Subject: [PATCH 2/3] implement delete objects --- .../s3/core/service/ObjectService.java | 2 +- .../s3/datatypes/response/S3Error.java | 6 ++++ .../s3/test/BucketIntegrationTest.java | 5 ++++ .../s3/test/ObjectIntegrationTest.java | 28 +++++++++++++++++++ .../s3/rest/handler/LocalS3RouterFactory.java | 16 +++++++++++ 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/local-s3-core/src/main/java/com/robothy/s3/core/service/ObjectService.java b/local-s3-core/src/main/java/com/robothy/s3/core/service/ObjectService.java index ef349df..98f3551 100644 --- a/local-s3-core/src/main/java/com/robothy/s3/core/service/ObjectService.java +++ b/local-s3-core/src/main/java/com/robothy/s3/core/service/ObjectService.java @@ -6,7 +6,7 @@ public interface ObjectService extends LocalS3MetadataApplicable, StorageApplicable, PutObjectService, GetObjectService, DeleteObjectService, ListObjectsService, ListObjectVersionsService, CreateMultipartUploadService, UploadPartService, CompleteMultipartUploadService, CopyObjectService, - ObjectTaggingService { + ObjectTaggingService, DeleteObjectsService { } diff --git a/local-s3-datatypes/src/main/java/com/robothy/s3/datatypes/response/S3Error.java b/local-s3-datatypes/src/main/java/com/robothy/s3/datatypes/response/S3Error.java index f6ace5c..3bdfd8c 100644 --- a/local-s3-datatypes/src/main/java/com/robothy/s3/datatypes/response/S3Error.java +++ b/local-s3-datatypes/src/main/java/com/robothy/s3/datatypes/response/S3Error.java @@ -35,4 +35,10 @@ public class S3Error { @JacksonXmlProperty(localName = "BucketName") private String bucketName; + + @JacksonXmlProperty(localName = "Key") + private String key; + + @JacksonXmlProperty(localName = "VersionId") + private String versionId; } diff --git a/local-s3-interationtest/src/test/java/com/robothy/s3/test/BucketIntegrationTest.java b/local-s3-interationtest/src/test/java/com/robothy/s3/test/BucketIntegrationTest.java index 410434c..7feb57b 100644 --- a/local-s3-interationtest/src/test/java/com/robothy/s3/test/BucketIntegrationTest.java +++ b/local-s3-interationtest/src/test/java/com/robothy/s3/test/BucketIntegrationTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -46,11 +47,15 @@ public class BucketIntegrationTest { @LocalS3 void testCreateBucket(AmazonS3 s3) { CreateBucketRequest bucketRequest = new CreateBucketRequest("my-bucket", "local"); + assertFalse(s3.doesBucketExistV2("my-bucket")); Bucket bucket = s3.createBucket(bucketRequest); assertEquals("my-bucket", bucket.getName()); + assertTrue(s3.doesBucketExistV2("my-bucket")); + assertFalse(s3.doesBucketExistV2("bucket2")); Bucket bucket2 = s3.createBucket("bucket2"); assertEquals("bucket2", bucket2.getName()); + assertTrue(s3.doesBucketExistV2("bucket2")); HeadBucketResult headBucketResult = s3.headBucket(new HeadBucketRequest("my-bucket")); assertEquals("local", headBucketResult.getBucketRegion()); diff --git a/local-s3-interationtest/src/test/java/com/robothy/s3/test/ObjectIntegrationTest.java b/local-s3-interationtest/src/test/java/com/robothy/s3/test/ObjectIntegrationTest.java index d18fe5e..a009e74 100644 --- a/local-s3-interationtest/src/test/java/com/robothy/s3/test/ObjectIntegrationTest.java +++ b/local-s3-interationtest/src/test/java/com/robothy/s3/test/ObjectIntegrationTest.java @@ -16,10 +16,13 @@ import com.amazonaws.services.s3.model.CopyObjectRequest; import com.amazonaws.services.s3.model.CopyObjectResult; import com.amazonaws.services.s3.model.DeleteObjectTaggingRequest; +import com.amazonaws.services.s3.model.DeleteObjectsRequest; +import com.amazonaws.services.s3.model.DeleteObjectsResult; import com.amazonaws.services.s3.model.GetObjectRequest; import com.amazonaws.services.s3.model.GetObjectTaggingRequest; import com.amazonaws.services.s3.model.GetObjectTaggingResult; import com.amazonaws.services.s3.model.ListObjectsRequest; +import com.amazonaws.services.s3.model.MultiObjectDeleteException; import com.amazonaws.services.s3.model.ObjectListing; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.ObjectTagging; @@ -46,7 +49,9 @@ public class ObjectIntegrationTest { @LocalS3 void testPutObject(AmazonS3 s3) throws IOException { Bucket bucket1 = s3.createBucket("bucket1"); + assertFalse(s3.doesObjectExist("bucket1", "hello.txt")); s3.putObject(bucket1.getName(), "hello.txt", "Hello"); + assertTrue(s3.doesObjectExist("bucket1", "hello.txt")); S3Object object = s3.getObject(bucket1.getName(), "hello.txt"); assertArrayEquals("Hello".getBytes(), object.getObjectContent().readAllBytes()); } @@ -313,4 +318,27 @@ void testObjectTagging(AmazonS3 s3) { assertDoesNotThrow(() -> s3.deleteObjectTagging(new DeleteObjectTaggingRequest(bucketName, key))); } + @LocalS3 + @Test + void testDeleteObjects(AmazonS3 s3) { + String bucketName = "my-bucket"; + s3.createBucket(bucketName); + assertDoesNotThrow(() -> s3.deleteObjects(new DeleteObjectsRequest(bucketName))); + + DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucketName); + deleteObjectsRequest.setKeys(List.of( + new DeleteObjectsRequest.KeyVersion("a.txt"), + new DeleteObjectsRequest.KeyVersion("b.txt", "123"))); + + assertThrows(MultiObjectDeleteException.class, () -> s3.deleteObjects(deleteObjectsRequest)); + + s3.putObject(bucketName, "a.txt", "Hello"); + s3.putObject(bucketName, "b.txt", "World"); + DeleteObjectsRequest deleteObjectsRequest1 = new DeleteObjectsRequest(bucketName); + deleteObjectsRequest1.setKeys(List.of(new DeleteObjectsRequest.KeyVersion("a.txt"), + new DeleteObjectsRequest.KeyVersion("b.txt"))); + DeleteObjectsResult deleteObjectsResult1 = s3.deleteObjects(deleteObjectsRequest1); + assertEquals(2, deleteObjectsResult1.getDeletedObjects().size()); + } + } diff --git a/local-s3-rest/src/main/java/com/robothy/s3/rest/handler/LocalS3RouterFactory.java b/local-s3-rest/src/main/java/com/robothy/s3/rest/handler/LocalS3RouterFactory.java index 17e821a..6bc04c3 100644 --- a/local-s3-rest/src/main/java/com/robothy/s3/rest/handler/LocalS3RouterFactory.java +++ b/local-s3-rest/src/main/java/com/robothy/s3/rest/handler/LocalS3RouterFactory.java @@ -70,6 +70,20 @@ public static Router create(ServiceFactory serviceFactory) { .handler(new CompleteMultipartUploadController(serviceFactory)) .build(); + Route DeleteObjects = Route.builder() + .method(HttpMethod.POST) + .path("/{bucket}") + .paramMatcher(params -> params.containsKey("delete")) + .handler(new DeleteObjectsController(serviceFactory)) + .build(); + + Route DeleteObjects_ = Route.builder() + .method(HttpMethod.POST) + .path("/{bucket}/") + .paramMatcher(params -> params.containsKey("delete")) + .handler(new DeleteObjectsController(serviceFactory)) + .build(); + Route PutObjectTagging = Route.builder() .method(HttpMethod.PUT) .path("/{bucket}/{*key}") @@ -377,6 +391,8 @@ public static Router create(ServiceFactory serviceFactory) { .route(DeleteBucketTagging) .route(DeleteBucketTagging_) .route(DeleteObject) + .route(DeleteObjects) + .route(DeleteObjects_) .route(DeleteObjectTagging) .route(GetBucket) .route(GetObject) From 4724d07f3efda3e1db4535b0098b31f69a29d9fa Mon Sep 17 00:00:00 2001 From: Luo Date: Sun, 1 Jan 2023 18:17:00 +0800 Subject: [PATCH 3/3] implement delete objects --- .../java/com/robothy/s3/datatypes/response/S3ErrorTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/local-s3-datatypes/src/test/java/com/robothy/s3/datatypes/response/S3ErrorTest.java b/local-s3-datatypes/src/test/java/com/robothy/s3/datatypes/response/S3ErrorTest.java index 5434638..6fb0d81 100644 --- a/local-s3-datatypes/src/test/java/com/robothy/s3/datatypes/response/S3ErrorTest.java +++ b/local-s3-datatypes/src/test/java/com/robothy/s3/datatypes/response/S3ErrorTest.java @@ -20,6 +20,8 @@ public void test() throws JsonProcessingException { .argumentName("Name") .argumentValue("Robothy") .bucketName("my-bucket") + .versionId("123") + .key("a.txt") .build(); String xml = xmlMapper.writeValueAsString(error);