Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement delete objects #38

Merged
merged 3 commits into from
Jan 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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