Skip to content

Commit

Permalink
implement delete objects (#38)
Browse files Browse the repository at this point in the history
* implement delete objects
  • Loading branch information
Robothy authored Jan 1, 2023
1 parent 1f7377c commit 7c31be2
Show file tree
Hide file tree
Showing 14 changed files with 408 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -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<Object> deleteObjects(String bucketName, DeleteObjectsRequest request) {

List<Object> 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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
public interface ObjectService extends LocalS3MetadataApplicable, StorageApplicable,
PutObjectService, GetObjectService, DeleteObjectService, ListObjectsService, ListObjectVersionsService,
CreateMultipartUploadService, UploadPartService, CompleteMultipartUploadService, CopyObjectService,
ObjectTaggingService {
ObjectTaggingService, DeleteObjectsService {


}
Original file line number Diff line number Diff line change
@@ -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<ObjectIdentifier> objectsToDelete = List.of(
new ObjectIdentifier("a.txt", "123"),
new ObjectIdentifier("b.txt", null)
);
DeleteObjectsRequest request1 = new DeleteObjectsRequest(objectsToDelete, false);

List<Object> 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<Object> results2 = objectService.deleteObjects(bucketName, request2);
assertEquals(1, results2.size());
assertInstanceOf(S3Error.class, results2.get(0));
}

}
Original file line number Diff line number Diff line change
@@ -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<String> getVersionId() {
return Optional.ofNullable(versionId);
}

}
Original file line number Diff line number Diff line change
@@ -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<ObjectIdentifier> objects = new ArrayList<>();

@JacksonXmlProperty(localName = "Quiet")
private boolean quiet;

}
Original file line number Diff line number Diff line change
@@ -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<Object> 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<DeleteResult> {

DeleteResultSerializer() {
this(null);
}

protected DeleteResultSerializer(Class<DeleteResult> 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();
}

}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
@@ -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 = "<Delete>\n" +
" <Object>\n" +
" <Key>a.txt</Key>\n" +
" <VersionId>null</VersionId>\n" +
" </Object>\n" +
" <Object>\n" +
" <Key>b.txt</Key>\n" +
" </Object>\n" +
" <Quiet>true</Quiet>\n" +
"</Delete>";

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());
}

}
Original file line number Diff line number Diff line change
@@ -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));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand Down
Loading

0 comments on commit 7c31be2

Please sign in to comment.