feat:增加内置oss
parent
34bb73c18c
commit
21894f1f57
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<artifactId>iot-iita-core</artifactId>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>iot-common-core</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>iot-iita-core</artifactId>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>iot-iita-core</artifactId>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>iot-iita-core</artifactId>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>iot-iita-core</artifactId>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
|
@ -89,7 +89,10 @@ public class OssClient {
|
|||
client.createBucket(createBucketRequest);
|
||||
client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType()));
|
||||
} catch (Exception e) {
|
||||
throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
|
||||
if (e.getMessage().contains("Your previous request to create the named bucket succeeded and you already own it")) {
|
||||
return;
|
||||
}
|
||||
throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,7 +113,7 @@ public class OssClient {
|
|||
putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
|
||||
client.putObject(putObjectRequest);
|
||||
} catch (Exception e) {
|
||||
throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
|
||||
throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]", e);
|
||||
}
|
||||
return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build();
|
||||
}
|
||||
|
@ -175,7 +178,11 @@ public class OssClient {
|
|||
if (StringUtils.isNotBlank(prefix)) {
|
||||
path = prefix + "/" + path;
|
||||
}
|
||||
return path + suffix;
|
||||
path = path + suffix;
|
||||
if (configKey.equals("oss-embed")) {
|
||||
return path.replace("/", "_");
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -15,4 +15,7 @@ public class OssException extends RuntimeException {
|
|||
super(msg);
|
||||
}
|
||||
|
||||
public OssException(String msg, Exception e) {
|
||||
super(msg, e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>iot-iita-core</artifactId>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>iot-iita-core</artifactId>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ public class SaTokenConfig implements WebMvcConfigurer {
|
|||
List<String> swaggerUrls = List.of("/doc.html","/favicon.ico", "/webjars/**", "/resources/**"
|
||||
, "/swagger-resources/**", "/swagger-ui.html/**");
|
||||
|
||||
List loginUrls = List.of("/code", "/auth/tenant/list", "/auth/login", "/auth/logout");
|
||||
List loginUrls = List.of("/code", "/auth/tenant/list", "/auth/login", "/auth/logout","/iot-oss/**");
|
||||
List<String> openApiUrls = List.of( "/openapi/v1/getToken");
|
||||
|
||||
List<String> excludeUrls = new ArrayList<>();
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>iot-iita-core</artifactId>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>iot-iita-core</artifactId>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>iot-iita-core</artifactId>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
|
@ -21,11 +21,12 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@Slf4j
|
||||
@ControllerAdvice
|
||||
@ControllerAdvice(annotations = RestController.class)
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
|
|
|
@ -14,6 +14,7 @@ import cn.dev33.satoken.util.SaResult;
|
|||
import cn.hutool.core.util.IdUtil;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
|
@ -27,6 +28,9 @@ import java.util.Map;
|
|||
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
|
||||
@Override
|
||||
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
if (returnType.getParameterType() == ResponseEntity.class) {
|
||||
return false;
|
||||
}
|
||||
return !converterType.equals(StringHttpMessageConverter.class);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>iot-iita-core</artifactId>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>iot-message-bus</artifactId>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>iot-message-bus</artifactId>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>iot-message-bus</artifactId>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>iot-iita-core</artifactId>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>pom</packaging>
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<artifactId>iot-iita-core</artifactId>
|
||||
<version>1.0.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>iot-oss-embed</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>cc.iotkit</groupId>
|
||||
<artifactId>iot-common-oss</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-xml</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jdk8</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>regions</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>utils</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.xml.bind</groupId>
|
||||
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,470 @@
|
|||
/*
|
||||
* Copyright 2017-2024 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock;
|
||||
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_BUCKET_OBJECT_LOCK_ENABLED;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.CONTINUATION_TOKEN;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.ENCODING_TYPE;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.KEY_MARKER;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.LIFECYCLE;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.LIST_TYPE_V2;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.LOCATION;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.MAX_KEYS;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.NOT_LIFECYCLE;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.NOT_LIST_TYPE;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.NOT_LOCATION;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.NOT_OBJECT_LOCK;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.NOT_UPLOADS;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.NOT_VERSIONS;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.OBJECT_LOCK;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.START_AFTER;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.VERSIONS;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.VERSION_ID_MARKER;
|
||||
import static org.springframework.http.MediaType.APPLICATION_XML_VALUE;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cc.iotkit.s3mock.dto.BucketLifecycleConfiguration;
|
||||
import cc.iotkit.s3mock.dto.ListAllMyBucketsResult;
|
||||
import cc.iotkit.s3mock.dto.ListBucketResult;
|
||||
import cc.iotkit.s3mock.dto.ListBucketResultV2;
|
||||
import cc.iotkit.s3mock.dto.ListVersionsResult;
|
||||
import cc.iotkit.s3mock.dto.LocationConstraint;
|
||||
import cc.iotkit.s3mock.dto.ObjectLockConfiguration;
|
||||
import cc.iotkit.s3mock.service.BucketService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
|
||||
/**
|
||||
* Handles requests related to buckets.
|
||||
*/
|
||||
@SaIgnore
|
||||
@CrossOrigin(originPatterns = "*", exposedHeaders = "*")
|
||||
@Controller
|
||||
@RequestMapping("${s3mock.contextPath:}")
|
||||
public class BucketController {
|
||||
private final BucketService bucketService;
|
||||
private final Region region;
|
||||
|
||||
public BucketController(BucketService bucketService, Region region) {
|
||||
this.bucketService = bucketService;
|
||||
this.region = region;
|
||||
}
|
||||
|
||||
//================================================================================================
|
||||
// /
|
||||
//================================================================================================
|
||||
|
||||
/**
|
||||
* List all existing buckets.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListBuckets.html">API Reference</a>
|
||||
*
|
||||
* @return List of all Buckets
|
||||
*/
|
||||
@GetMapping(
|
||||
value = "/",
|
||||
produces = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<ListAllMyBucketsResult> listBuckets() {
|
||||
var listAllMyBucketsResult = bucketService.listBuckets();
|
||||
return ResponseEntity.ok(listAllMyBucketsResult);
|
||||
}
|
||||
|
||||
//================================================================================================
|
||||
// /{bucketName:.+}
|
||||
//================================================================================================
|
||||
|
||||
/**
|
||||
* Create a bucket if the name matches a simplified version of the bucket naming rules.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html">API Reference Bucket Naming</a>
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName name of the bucket that should be created.
|
||||
*
|
||||
* @return 200 OK if creation was successful.
|
||||
*/
|
||||
@PutMapping(
|
||||
value = {
|
||||
//AWS SDK V2 pattern
|
||||
"/{bucketName:.+}",
|
||||
//AWS SDK V1 pattern
|
||||
"/{bucketName:.+}/"
|
||||
},
|
||||
params = {
|
||||
NOT_OBJECT_LOCK,
|
||||
NOT_LIFECYCLE
|
||||
}
|
||||
)
|
||||
public ResponseEntity<Void> createBucket(@PathVariable final String bucketName,
|
||||
@RequestHeader(value = X_AMZ_BUCKET_OBJECT_LOCK_ENABLED,
|
||||
required = false, defaultValue = "false") boolean objectLockEnabled) {
|
||||
bucketService.verifyBucketNameIsAllowed(bucketName);
|
||||
bucketService.verifyBucketDoesNotExist(bucketName);
|
||||
bucketService.createBucket(bucketName, objectLockEnabled);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a bucket exists.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadBucket.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName name of the Bucket.
|
||||
*
|
||||
* @return 200 if it exists; 404 if not found.
|
||||
*/
|
||||
@RequestMapping(
|
||||
value = {
|
||||
//AWS SDK V2 pattern
|
||||
"/{bucketName:.+}",
|
||||
//AWS SDK V1 pattern
|
||||
"/{bucketName:.+}/"
|
||||
},
|
||||
method = RequestMethod.HEAD
|
||||
)
|
||||
public ResponseEntity<Void> headBucket(@PathVariable final String bucketName) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
//return bucket region
|
||||
//return bucket location
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a bucket.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucket.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName name of the Bucket.
|
||||
*
|
||||
* @return 204 if Bucket was deleted; 404 if not found
|
||||
*/
|
||||
@DeleteMapping(
|
||||
value = {
|
||||
//AWS SDK V2 pattern
|
||||
"/{bucketName:.+}",
|
||||
//AWS SDK V1 pattern
|
||||
"/{bucketName:.+}/"
|
||||
},
|
||||
params = {
|
||||
NOT_LIFECYCLE
|
||||
}
|
||||
)
|
||||
public ResponseEntity<Void> deleteBucket(@PathVariable String bucketName) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
bucketService.verifyBucketIsEmpty(bucketName);
|
||||
bucketService.deleteBucket(bucketName);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ObjectLockConfiguration of a bucket.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectLockConfiguration.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName name of the Bucket.
|
||||
*
|
||||
* @return 200, ObjectLockConfiguration
|
||||
*/
|
||||
@GetMapping(
|
||||
value = {
|
||||
//AWS SDK V2 pattern
|
||||
"/{bucketName:.+}",
|
||||
//AWS SDK V1 pattern
|
||||
"/{bucketName:.+}/"
|
||||
},
|
||||
params = {
|
||||
OBJECT_LOCK,
|
||||
NOT_LIST_TYPE
|
||||
},
|
||||
produces = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<ObjectLockConfiguration> getObjectLockConfiguration(
|
||||
@PathVariable String bucketName) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
var configuration = bucketService.getObjectLockConfiguration(bucketName);
|
||||
return ResponseEntity.ok(configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put ObjectLockConfiguration of a bucket.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectLockConfiguration.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName name of the Bucket.
|
||||
*
|
||||
* @return 200, ObjectLockConfiguration
|
||||
*/
|
||||
@PutMapping(
|
||||
value = {
|
||||
//AWS SDK V2 pattern
|
||||
"/{bucketName:.+}",
|
||||
//AWS SDK V1 pattern
|
||||
"/{bucketName:.+}/"
|
||||
},
|
||||
params = {
|
||||
OBJECT_LOCK
|
||||
},
|
||||
consumes = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<Void> putObjectLockConfiguration(
|
||||
@PathVariable String bucketName,
|
||||
@RequestBody ObjectLockConfiguration configuration) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
bucketService.setObjectLockConfiguration(bucketName, configuration);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get BucketLifecycleConfiguration of a bucket.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName name of the Bucket.
|
||||
*
|
||||
* @return 200, ObjectLockConfiguration
|
||||
*/
|
||||
@GetMapping(
|
||||
value = {
|
||||
//AWS SDK V2 pattern
|
||||
"/{bucketName:.+}",
|
||||
//AWS SDK V1 pattern
|
||||
"/{bucketName:.+}/"
|
||||
},
|
||||
params = {
|
||||
LIFECYCLE,
|
||||
NOT_LIST_TYPE
|
||||
},
|
||||
produces = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<BucketLifecycleConfiguration> getBucketLifecycleConfiguration(
|
||||
@PathVariable String bucketName) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
var configuration = bucketService.getBucketLifecycleConfiguration(bucketName);
|
||||
return ResponseEntity.ok(configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put BucketLifecycleConfiguration of a bucket.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName name of the Bucket.
|
||||
*
|
||||
* @return 200, ObjectLockConfiguration
|
||||
*/
|
||||
@PutMapping(
|
||||
value = {
|
||||
//AWS SDK V2 pattern
|
||||
"/{bucketName:.+}",
|
||||
//AWS SDK V1 pattern
|
||||
"/{bucketName:.+}/"
|
||||
},
|
||||
params = {
|
||||
LIFECYCLE
|
||||
},
|
||||
consumes = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<Void> putBucketLifecycleConfiguration(
|
||||
@PathVariable String bucketName,
|
||||
@RequestBody BucketLifecycleConfiguration configuration) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
bucketService.setBucketLifecycleConfiguration(bucketName, configuration);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete BucketLifecycleConfiguration of a bucket.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketLifecycle.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName name of the Bucket.
|
||||
*
|
||||
* @return 200, ObjectLockConfiguration
|
||||
*/
|
||||
@DeleteMapping(
|
||||
value = {
|
||||
//AWS SDK V2 pattern
|
||||
"/{bucketName:.+}",
|
||||
//AWS SDK V1 pattern
|
||||
"/{bucketName:.+}/"
|
||||
},
|
||||
params = {
|
||||
LIFECYCLE
|
||||
}
|
||||
)
|
||||
public ResponseEntity<Void> deleteBucketLifecycleConfiguration(
|
||||
@PathVariable String bucketName) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
bucketService.deleteBucketLifecycleConfiguration(bucketName);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get location of a bucket.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName name of the Bucket.
|
||||
*
|
||||
* @return 200, LocationConstraint
|
||||
*/
|
||||
@GetMapping(
|
||||
value = "/{bucketName:.+}",
|
||||
params = {
|
||||
LOCATION
|
||||
}
|
||||
)
|
||||
public ResponseEntity<LocationConstraint> getBucketLocation(
|
||||
@PathVariable String bucketName) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
return ResponseEntity.ok(new LocationConstraint(region));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve list of objects of a bucket.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjects.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName {@link String} set bucket name
|
||||
* @param prefix {@link String} find object names they start with prefix
|
||||
* @param encodingType whether to use URL encoding (encodingtype="url") or not
|
||||
*
|
||||
* @return {@link ListBucketResult} a list of objects in Bucket
|
||||
* @deprecated Long since replaced by ListObjectsV2, {@see #listObjectsInsideBucketV2}
|
||||
*/
|
||||
@GetMapping(
|
||||
value = {
|
||||
//AWS SDK V2 pattern
|
||||
"/{bucketName:.+}",
|
||||
//AWS SDK V1 pattern
|
||||
"/{bucketName:.+}/"
|
||||
},
|
||||
params = {
|
||||
NOT_UPLOADS,
|
||||
NOT_OBJECT_LOCK,
|
||||
NOT_LIST_TYPE,
|
||||
NOT_LIFECYCLE,
|
||||
NOT_LOCATION,
|
||||
NOT_VERSIONS
|
||||
},
|
||||
produces = APPLICATION_XML_VALUE
|
||||
)
|
||||
@Deprecated(since = "2.12.2", forRemoval = true)
|
||||
public ResponseEntity<ListBucketResult> listObjects(
|
||||
@PathVariable String bucketName,
|
||||
@RequestParam(required = false) String prefix,
|
||||
@RequestParam(required = false) String delimiter,
|
||||
@RequestParam(required = false) String marker,
|
||||
@RequestParam(name = ENCODING_TYPE, required = false) String encodingType,
|
||||
@RequestParam(name = MAX_KEYS, defaultValue = "1000", required = false) Integer maxKeys) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
bucketService.verifyMaxKeys(maxKeys);
|
||||
bucketService.verifyEncodingType(encodingType);
|
||||
var listBucketResult = bucketService.listObjectsV1(bucketName, prefix, delimiter,
|
||||
marker, encodingType, maxKeys);
|
||||
return ResponseEntity.ok(listBucketResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve list of objects of a bucket.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName {@link String} set bucket name
|
||||
* @param prefix {@link String} find object names they start with prefix
|
||||
* @param startAfter {@link String} return key names after a specific object key in your key
|
||||
* space
|
||||
* @param maxKeys {@link Integer} set maximum number of keys to be returned
|
||||
* @param continuationToken {@link String} pagination token returned by previous request
|
||||
*
|
||||
* @return {@link ListBucketResultV2} a list of objects in Bucket
|
||||
*/
|
||||
@GetMapping(
|
||||
value = {
|
||||
//AWS SDK V2 pattern
|
||||
"/{bucketName:.+}",
|
||||
//AWS SDK V1 pattern
|
||||
"/{bucketName:.+}/"
|
||||
},
|
||||
params = {
|
||||
LIST_TYPE_V2
|
||||
},
|
||||
produces = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<ListBucketResultV2> listObjectsV2(
|
||||
@PathVariable String bucketName,
|
||||
@RequestParam(required = false) String prefix,
|
||||
@RequestParam(required = false) String delimiter,
|
||||
@RequestParam(name = ENCODING_TYPE, required = false) String encodingType,
|
||||
@RequestParam(name = START_AFTER, required = false) String startAfter,
|
||||
@RequestParam(name = MAX_KEYS, defaultValue = "1000", required = false) Integer maxKeys,
|
||||
@RequestParam(name = CONTINUATION_TOKEN, required = false) String continuationToken) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
bucketService.verifyMaxKeys(maxKeys);
|
||||
bucketService.verifyEncodingType(encodingType);
|
||||
var listBucketResultV2 =
|
||||
bucketService.listObjectsV2(bucketName, prefix, delimiter, encodingType, startAfter,
|
||||
maxKeys, continuationToken);
|
||||
|
||||
return ResponseEntity.ok(listBucketResultV2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve list of versions of an object of a bucket.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectVersions.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName {@link String} set bucket name
|
||||
* @param prefix {@link String} find object names they start with prefix
|
||||
* @param startAfter {@link String} return key names after a specific object key in your key
|
||||
* space
|
||||
* @param maxKeys {@link Integer} set maximum number of keys to be returned
|
||||
* @param continuationToken {@link String} pagination token returned by previous request
|
||||
*
|
||||
* @return {@link ListVersionsResult} a list of objects in Bucket
|
||||
*/
|
||||
@GetMapping(
|
||||
value = {
|
||||
//AWS SDK V2 pattern
|
||||
"/{bucketName:.+}",
|
||||
//AWS SDK V1 pattern
|
||||
"/{bucketName:.+}/"
|
||||
},
|
||||
params = {
|
||||
VERSIONS
|
||||
},
|
||||
produces = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<ListVersionsResult> listObjectVersions(
|
||||
@PathVariable String bucketName,
|
||||
@RequestParam(required = false) String prefix,
|
||||
@RequestParam(required = false) String delimiter,
|
||||
@RequestParam(name = KEY_MARKER, required = false) String keyMarker,
|
||||
@RequestParam(name = VERSION_ID_MARKER, required = false) String versionIdMarker,
|
||||
@RequestParam(name = ENCODING_TYPE, required = false) String encodingType,
|
||||
@RequestParam(name = START_AFTER, required = false) String startAfter,
|
||||
@RequestParam(name = MAX_KEYS, defaultValue = "1000", required = false) Integer maxKeys,
|
||||
@RequestParam(name = CONTINUATION_TOKEN, required = false) String continuationToken) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
bucketService.verifyMaxKeys(maxKeys);
|
||||
bucketService.verifyEncodingType(encodingType);
|
||||
var listVersionsResult =
|
||||
bucketService.listVersions(bucketName, prefix, delimiter, encodingType, startAfter,
|
||||
maxKeys, continuationToken, keyMarker, versionIdMarker);
|
||||
|
||||
return ResponseEntity.ok(listVersionsResult);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
/**
|
||||
* Spring Boot 2.2+ does not include the default favicon.ico anymore.
|
||||
* This is needed to check if the S3 Mock is up (at least in our examples and some use-cases)
|
||||
*/
|
||||
@SaIgnore
|
||||
@Controller
|
||||
@RequestMapping
|
||||
class FaviconController {
|
||||
@GetMapping("favicon.ico")
|
||||
@ResponseBody
|
||||
void favicon() {
|
||||
// Method is intentionally empty.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpRange;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
public class HttpRangeHeaderConverter implements Converter<String, HttpRange> {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public HttpRange convert(@NonNull String source) {
|
||||
var httpRanges = HttpRange.parseRanges(source);
|
||||
if (!httpRanges.isEmpty()) {
|
||||
return httpRanges.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock;
|
||||
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_SERVER_SIDE_ENCRYPTION;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
|
||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
||||
import static org.springframework.http.MediaType.APPLICATION_XML_VALUE;
|
||||
|
||||
import cc.iotkit.s3mock.dto.ErrorResponse;
|
||||
import cc.iotkit.s3mock.store.KmsKeyStore;
|
||||
import java.io.IOException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* A Filter that validates KMS keys of incoming Requests. If Keys can not be found in Keystore the
|
||||
* Request will be denied immediately.
|
||||
*/
|
||||
class KmsValidationFilter extends OncePerRequestFilter {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(KmsValidationFilter.class);
|
||||
|
||||
private static final String AWS_KMS = "aws:kms";
|
||||
|
||||
private final KmsKeyStore keystore;
|
||||
|
||||
private final MappingJackson2XmlHttpMessageConverter messageConverter;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link KmsValidationFilter}.
|
||||
*
|
||||
* @param keystore Keystore for validation of KMS Keys
|
||||
*/
|
||||
KmsValidationFilter(KmsKeyStore keystore,
|
||||
MappingJackson2XmlHttpMessageConverter messageConverter) {
|
||||
this.keystore = keystore;
|
||||
this.messageConverter = messageConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain) throws ServletException, IOException {
|
||||
try {
|
||||
LOG.debug("Checking KMS key, if present.");
|
||||
var encryptionTypeHeader = request.getHeader(X_AMZ_SERVER_SIDE_ENCRYPTION);
|
||||
var encryptionKeyId = request.getHeader(X_AMZ_SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID);
|
||||
|
||||
if (AWS_KMS.equals(encryptionTypeHeader)
|
||||
&& !isBlank(encryptionKeyId)
|
||||
&& !keystore.validateKeyId(encryptionKeyId)) {
|
||||
LOG.info("Received invalid KMS key ID {}. Sending error response.", encryptionKeyId);
|
||||
|
||||
request.getInputStream().close();
|
||||
|
||||
response.setStatus(BAD_REQUEST.value());
|
||||
response.setHeader(CONTENT_TYPE, APPLICATION_XML_VALUE);
|
||||
|
||||
var errorResponse = new ErrorResponse(
|
||||
"KMS.NotFoundException",
|
||||
"Key ID " + encryptionKeyId + " does not exist!",
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
messageConverter.getObjectMapper().writeValue(response.getOutputStream(), errorResponse);
|
||||
|
||||
response.flushBuffer();
|
||||
} else if (AWS_KMS.equals(encryptionTypeHeader)
|
||||
&& !isBlank(encryptionKeyId)
|
||||
&& keystore.validateKeyId(encryptionKeyId)) {
|
||||
LOG.info("Received valid KMS key ID {}.", encryptionKeyId);
|
||||
filterChain.doFilter(request, response);
|
||||
} else {
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
} finally {
|
||||
LOG.debug("Finished checking KMS key.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,375 @@
|
|||
/*
|
||||
* Copyright 2017-2024 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock;
|
||||
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.NOT_X_AMZ_COPY_SOURCE;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.NOT_X_AMZ_COPY_SOURCE_RANGE;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_CONTENT_SHA256;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_COPY_SOURCE;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_COPY_SOURCE_IF_MATCH;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_COPY_SOURCE_IF_NONE_MATCH;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_COPY_SOURCE_RANGE;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_STORAGE_CLASS;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.NOT_LIFECYCLE;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.PART_NUMBER;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.UPLOADS;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.UPLOAD_ID;
|
||||
import static cc.iotkit.s3mock.util.HeaderUtil.checksumAlgorithmFrom;
|
||||
import static cc.iotkit.s3mock.util.HeaderUtil.checksumFrom;
|
||||
import static cc.iotkit.s3mock.util.HeaderUtil.encryptionHeadersFrom;
|
||||
import static cc.iotkit.s3mock.util.HeaderUtil.isV4ChunkedWithSigningEnabled;
|
||||
import static cc.iotkit.s3mock.util.HeaderUtil.storeHeadersFrom;
|
||||
import static cc.iotkit.s3mock.util.HeaderUtil.userMetadataFrom;
|
||||
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
|
||||
import static org.springframework.http.MediaType.APPLICATION_XML_VALUE;
|
||||
|
||||
import cc.iotkit.s3mock.dto.*;
|
||||
import cc.iotkit.s3mock.dto.CompleteMultipartUpload;
|
||||
import cc.iotkit.s3mock.dto.CompleteMultipartUploadResult;
|
||||
import cc.iotkit.s3mock.dto.CopyPartResult;
|
||||
import cc.iotkit.s3mock.dto.CopySource;
|
||||
import cc.iotkit.s3mock.dto.InitiateMultipartUploadResult;
|
||||
import cc.iotkit.s3mock.dto.ListMultipartUploadsResult;
|
||||
import cc.iotkit.s3mock.dto.ListPartsResult;
|
||||
import cc.iotkit.s3mock.dto.ObjectKey;
|
||||
import cc.iotkit.s3mock.dto.StorageClass;
|
||||
import cc.iotkit.s3mock.service.BucketService;
|
||||
import cc.iotkit.s3mock.service.MultipartService;
|
||||
import cc.iotkit.s3mock.service.ObjectService;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpRange;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import software.amazon.awssdk.utils.http.SdkHttpUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* Handles requests related to parts.
|
||||
*/
|
||||
@CrossOrigin(originPatterns = "*", exposedHeaders = "*")
|
||||
@Controller
|
||||
@RequestMapping("${s3mock.contextPath:}")
|
||||
public class MultipartController {
|
||||
|
||||
private final BucketService bucketService;
|
||||
private final ObjectService objectService;
|
||||
private final MultipartService multipartService;
|
||||
|
||||
public MultipartController(BucketService bucketService, ObjectService objectService,
|
||||
MultipartService multipartService) {
|
||||
this.bucketService = bucketService;
|
||||
this.objectService = objectService;
|
||||
this.multipartService = multipartService;
|
||||
}
|
||||
|
||||
//================================================================================================
|
||||
// /{bucketName:.+}
|
||||
//================================================================================================
|
||||
|
||||
/**
|
||||
* Lists all in-progress multipart uploads.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListMultipartUploads.html">API Reference</a>
|
||||
*
|
||||
* <p>Not yet supported request parameters: delimiter, encoding-type, max-uploads, key-marker,
|
||||
* upload-id-marker.</p>
|
||||
*
|
||||
* @param bucketName the Bucket in which to store the file in.
|
||||
*
|
||||
* @return the {@link ListMultipartUploadsResult}
|
||||
*/
|
||||
@GetMapping(
|
||||
value = {
|
||||
//AWS SDK V2 pattern
|
||||
"/{bucketName:.+}",
|
||||
//AWS SDK V1 pattern
|
||||
"/{bucketName:.+}/"
|
||||
},
|
||||
params = {
|
||||
UPLOADS
|
||||
},
|
||||
produces = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<ListMultipartUploadsResult> listMultipartUploads(
|
||||
@PathVariable String bucketName,
|
||||
@RequestParam(required = false) String prefix) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
|
||||
return ResponseEntity.ok(multipartService.listMultipartUploads(bucketName, prefix));
|
||||
}
|
||||
|
||||
//================================================================================================
|
||||
// /{bucketName:.+}/{key}
|
||||
//================================================================================================
|
||||
|
||||
/**
|
||||
* Aborts a multipart upload for a given uploadId.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_AbortMultipartUpload.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName the Bucket in which to store the file in.
|
||||
* @param uploadId id of the upload. Has to match all other part's uploads.
|
||||
*/
|
||||
@DeleteMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
params = {
|
||||
UPLOAD_ID,
|
||||
NOT_LIFECYCLE
|
||||
},
|
||||
produces = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<Void> abortMultipartUpload(@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key,
|
||||
@RequestParam String uploadId) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
multipartService.verifyMultipartUploadExists(uploadId);
|
||||
multipartService.abortMultipartUpload(bucketName, key.getKey(), uploadId);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all parts a file multipart upload.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListParts.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName the Bucket in which to store the file in.
|
||||
* @param uploadId id of the upload. Has to match all other part's uploads.
|
||||
*
|
||||
* @return the {@link ListPartsResult}
|
||||
*/
|
||||
@GetMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
params = {
|
||||
UPLOAD_ID
|
||||
},
|
||||
produces = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<ListPartsResult> listParts(@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key,
|
||||
@RequestParam String uploadId) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
multipartService.verifyMultipartUploadExists(uploadId);
|
||||
|
||||
return ResponseEntity
|
||||
.ok(multipartService.getMultipartUploadParts(bucketName, key.getKey(), uploadId));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds an object to a bucket accepting encryption headers.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPart.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName the Bucket in which to store the file in.
|
||||
* @param uploadId id of the upload. Has to match all other part's uploads.
|
||||
* @param partNumber number of the part to upload.
|
||||
*
|
||||
* @return the etag of the uploaded part.
|
||||
*
|
||||
*/
|
||||
@PutMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
params = {
|
||||
UPLOAD_ID,
|
||||
PART_NUMBER
|
||||
},
|
||||
headers = {
|
||||
NOT_X_AMZ_COPY_SOURCE,
|
||||
NOT_X_AMZ_COPY_SOURCE_RANGE
|
||||
}
|
||||
)
|
||||
public ResponseEntity<Void> uploadPart(@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key,
|
||||
@RequestParam String uploadId,
|
||||
@RequestParam String partNumber,
|
||||
@RequestHeader(value = X_AMZ_CONTENT_SHA256, required = false) String sha256Header,
|
||||
@RequestHeader HttpHeaders httpHeaders,
|
||||
InputStream inputStream) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
multipartService.verifyMultipartUploadExists(uploadId);
|
||||
multipartService.verifyPartNumberLimits(partNumber);
|
||||
|
||||
var checksum = checksumFrom(httpHeaders);
|
||||
var checksumAlgorithm = checksumAlgorithmFrom(httpHeaders);
|
||||
|
||||
//persist checksum per part
|
||||
var etag = multipartService.putPart(bucketName,
|
||||
key.getKey(),
|
||||
uploadId,
|
||||
partNumber,
|
||||
inputStream,
|
||||
isV4ChunkedWithSigningEnabled(sha256Header),
|
||||
encryptionHeadersFrom(httpHeaders));
|
||||
|
||||
//return checksum headers
|
||||
//return encryption headers
|
||||
return ResponseEntity.ok().eTag("\"" + etag + "\"").build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a part by copying data from an existing object as data source.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPartCopy.html">API Reference</a>
|
||||
*
|
||||
* @param copySource References the Objects to be copied.
|
||||
* @param copyRange Defines the byte range for this part. Optional.
|
||||
* @param uploadId id of the upload. Has to match all other part's uploads.
|
||||
* @param partNumber number of the part to upload.
|
||||
*
|
||||
* @return The etag of the uploaded part.
|
||||
*
|
||||
*/
|
||||
@PutMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
headers = {
|
||||
X_AMZ_COPY_SOURCE,
|
||||
},
|
||||
params = {
|
||||
UPLOAD_ID,
|
||||
PART_NUMBER
|
||||
},
|
||||
produces = APPLICATION_XML_VALUE)
|
||||
public ResponseEntity<CopyPartResult> uploadPartCopy(
|
||||
@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key,
|
||||
@RequestHeader(value = X_AMZ_COPY_SOURCE) CopySource copySource,
|
||||
@RequestHeader(value = X_AMZ_COPY_SOURCE_RANGE, required = false) HttpRange copyRange,
|
||||
@RequestHeader(value = X_AMZ_COPY_SOURCE_IF_MATCH, required = false) List<String> match,
|
||||
@RequestHeader(value = X_AMZ_COPY_SOURCE_IF_NONE_MATCH,
|
||||
required = false) List<String> noneMatch,
|
||||
@RequestParam String uploadId,
|
||||
@RequestParam String partNumber,
|
||||
@RequestHeader HttpHeaders httpHeaders) {
|
||||
//TODO: needs modified-since handling, see API
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
multipartService.verifyPartNumberLimits(partNumber);
|
||||
var s3ObjectMetadata = objectService.verifyObjectExists(copySource.getBucket(), copySource.getKey());
|
||||
objectService.verifyObjectMatchingForCopy(match, noneMatch, s3ObjectMetadata);
|
||||
|
||||
var result = multipartService.copyPart(copySource.getBucket(),
|
||||
copySource.getKey(),
|
||||
copyRange,
|
||||
partNumber,
|
||||
bucketName,
|
||||
key.getKey(),
|
||||
uploadId,
|
||||
encryptionHeadersFrom(httpHeaders)
|
||||
);
|
||||
|
||||
//return encryption headers
|
||||
//return source version id
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a multipart upload accepting encryption headers.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateMultipartUpload.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName the Bucket in which to store the file in.
|
||||
*
|
||||
* @return the {@link InitiateMultipartUploadResult}.
|
||||
*/
|
||||
@PostMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
params = {
|
||||
UPLOADS
|
||||
},
|
||||
produces = APPLICATION_XML_VALUE)
|
||||
public ResponseEntity<InitiateMultipartUploadResult> createMultipartUpload(
|
||||
@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key,
|
||||
@RequestHeader(value = CONTENT_TYPE, required = false) String contentType,
|
||||
@RequestHeader(value = X_AMZ_STORAGE_CLASS, required = false, defaultValue = "STANDARD")
|
||||
StorageClass storageClass,
|
||||
@RequestHeader HttpHeaders httpHeaders) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
|
||||
var checksum = checksumFrom(httpHeaders);
|
||||
var checksumAlgorithm = checksumAlgorithmFrom(httpHeaders);
|
||||
|
||||
var uploadId = UUID.randomUUID().toString();
|
||||
var result =
|
||||
multipartService.prepareMultipartUpload(bucketName,
|
||||
key.getKey(),
|
||||
contentType,
|
||||
storeHeadersFrom(httpHeaders),
|
||||
uploadId,
|
||||
Owner.DEFAULT_OWNER,
|
||||
Owner.DEFAULT_OWNER,
|
||||
userMetadataFrom(httpHeaders),
|
||||
encryptionHeadersFrom(httpHeaders),
|
||||
storageClass,
|
||||
checksum,
|
||||
checksumAlgorithm);
|
||||
|
||||
//return encryption headers
|
||||
//return checksum algorithm headers
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an object to a bucket.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName the Bucket in which to store the file in.
|
||||
* @param uploadId id of the upload. Has to match all other part's uploads.
|
||||
*
|
||||
* @return {@link CompleteMultipartUploadResult}
|
||||
*/
|
||||
@PostMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
params = {
|
||||
UPLOAD_ID
|
||||
},
|
||||
produces = APPLICATION_XML_VALUE)
|
||||
public ResponseEntity<CompleteMultipartUploadResult> completeMultipartUpload(
|
||||
@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key,
|
||||
@RequestParam String uploadId,
|
||||
@RequestBody CompleteMultipartUpload upload,
|
||||
HttpServletRequest request,
|
||||
@RequestHeader HttpHeaders httpHeaders) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
multipartService.verifyMultipartUploadExists(uploadId);
|
||||
multipartService.verifyMultipartParts(bucketName, key.getKey(), uploadId, upload.getParts());
|
||||
var objectName = key.getKey();
|
||||
var locationWithEncodedKey = request
|
||||
.getRequestURL()
|
||||
.toString()
|
||||
.replace(objectName, SdkHttpUtils.urlEncode(objectName));
|
||||
|
||||
var result = multipartService.completeMultipartUpload(bucketName,
|
||||
key.getKey(),
|
||||
uploadId,
|
||||
upload.getParts(),
|
||||
encryptionHeadersFrom(httpHeaders),
|
||||
locationWithEncodedKey);
|
||||
|
||||
//return checksum and encryption headers.
|
||||
//return version id
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2017-2024 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock;
|
||||
|
||||
import cc.iotkit.s3mock.util.AwsHttpHeaders;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import software.amazon.awssdk.services.s3.model.ObjectCannedACL;
|
||||
|
||||
/**
|
||||
* Converts values of the {@link AwsHttpHeaders#X_AMZ_ACL} which is sent by the Amazon client.
|
||||
* Example: x-amz-acl: private
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html">API Reference</a>
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html#canned-acl">API Reference</a>
|
||||
*/
|
||||
class ObjectCannedAclHeaderConverter implements Converter<String, ObjectCannedACL> {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public ObjectCannedACL convert(@NonNull String source) {
|
||||
return ObjectCannedACL.fromValue(source);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,745 @@
|
|||
/*
|
||||
* Copyright 2017-2024 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock;
|
||||
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.CONTENT_MD5;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.MetadataDirective.METADATA_DIRECTIVE_COPY;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.NOT_X_AMZ_COPY_SOURCE;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.RANGE;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_ACL;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_CONTENT_SHA256;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_COPY_SOURCE;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_COPY_SOURCE_IF_MATCH;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_COPY_SOURCE_IF_NONE_MATCH;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_DELETE_MARKER;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_METADATA_DIRECTIVE;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_OBJECT_ATTRIBUTES;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_STORAGE_CLASS;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpHeaders.X_AMZ_TAGGING;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.ACL;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.ATTRIBUTES;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.DELETE;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.LEGAL_HOLD;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.NOT_ACL;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.NOT_ATTRIBUTES;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.NOT_LEGAL_HOLD;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.NOT_LIFECYCLE;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.NOT_RETENTION;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.NOT_TAGGING;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.NOT_UPLOADS;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.NOT_UPLOAD_ID;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.RETENTION;
|
||||
import static cc.iotkit.s3mock.util.AwsHttpParameters.TAGGING;
|
||||
import static cc.iotkit.s3mock.util.HeaderUtil.checksumAlgorithmFrom;
|
||||
import static cc.iotkit.s3mock.util.HeaderUtil.checksumFrom;
|
||||
import static cc.iotkit.s3mock.util.HeaderUtil.checksumHeaderFrom;
|
||||
import static cc.iotkit.s3mock.util.HeaderUtil.encryptionHeadersFrom;
|
||||
import static cc.iotkit.s3mock.util.HeaderUtil.isV4ChunkedWithSigningEnabled;
|
||||
import static cc.iotkit.s3mock.util.HeaderUtil.mediaTypeFrom;
|
||||
import static cc.iotkit.s3mock.util.HeaderUtil.overrideHeadersFrom;
|
||||
import static cc.iotkit.s3mock.util.HeaderUtil.storeHeadersFrom;
|
||||
import static cc.iotkit.s3mock.util.HeaderUtil.userMetadataFrom;
|
||||
import static cc.iotkit.s3mock.util.HeaderUtil.userMetadataHeadersFrom;
|
||||
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
|
||||
import static org.springframework.http.HttpHeaders.IF_MATCH;
|
||||
import static org.springframework.http.HttpHeaders.IF_NONE_MATCH;
|
||||
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
||||
import static org.springframework.http.HttpStatus.PARTIAL_CONTENT;
|
||||
import static org.springframework.http.HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE;
|
||||
import static org.springframework.http.MediaType.APPLICATION_XML_VALUE;
|
||||
|
||||
import cc.iotkit.s3mock.service.ObjectService;
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cc.iotkit.s3mock.dto.AccessControlPolicy;
|
||||
import cc.iotkit.s3mock.dto.CopyObjectResult;
|
||||
import cc.iotkit.s3mock.dto.CopySource;
|
||||
import cc.iotkit.s3mock.dto.Delete;
|
||||
import cc.iotkit.s3mock.dto.DeleteResult;
|
||||
import cc.iotkit.s3mock.dto.GetObjectAttributesOutput;
|
||||
import cc.iotkit.s3mock.dto.LegalHold;
|
||||
import cc.iotkit.s3mock.dto.ObjectAttributes;
|
||||
import cc.iotkit.s3mock.dto.ObjectKey;
|
||||
import cc.iotkit.s3mock.dto.Owner;
|
||||
import cc.iotkit.s3mock.dto.Retention;
|
||||
import cc.iotkit.s3mock.dto.StorageClass;
|
||||
import cc.iotkit.s3mock.dto.Tag;
|
||||
import cc.iotkit.s3mock.dto.TagSet;
|
||||
import cc.iotkit.s3mock.dto.Tagging;
|
||||
import cc.iotkit.s3mock.service.BucketService;
|
||||
import cc.iotkit.s3mock.store.S3ObjectMetadata;
|
||||
import cc.iotkit.s3mock.util.AwsHttpHeaders.MetadataDirective;
|
||||
import cc.iotkit.s3mock.util.CannedAclUtil;
|
||||
import cc.iotkit.s3mock.util.XmlUtil;
|
||||
import jakarta.xml.bind.JAXBException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.input.BoundedInputStream;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpRange;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
import software.amazon.awssdk.services.s3.model.ObjectCannedACL;
|
||||
|
||||
/**
|
||||
* Handles requests related to objects.
|
||||
*/
|
||||
@SaIgnore
|
||||
@CrossOrigin(originPatterns = "*", exposedHeaders = "*")
|
||||
@Controller
|
||||
@RequestMapping("${s3mock.contextPath:}")
|
||||
public class ObjectController {
|
||||
private static final String RANGES_BYTES = "bytes";
|
||||
|
||||
private final BucketService bucketService;
|
||||
private final ObjectService objectService;
|
||||
|
||||
public ObjectController(BucketService bucketService, ObjectService objectService) {
|
||||
this.bucketService = bucketService;
|
||||
this.objectService = objectService;
|
||||
}
|
||||
|
||||
//================================================================================================
|
||||
// /{bucketName:.+}
|
||||
//================================================================================================
|
||||
|
||||
/**
|
||||
* This operation removes multiple objects.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName name of bucket containing the object.
|
||||
* @param body The delete request.
|
||||
* @return The {@link DeleteResult}
|
||||
*/
|
||||
@PostMapping(
|
||||
value = {
|
||||
//AWS SDK V2 pattern
|
||||
"/{bucketName:.+}",
|
||||
//AWS SDK V1 pattern
|
||||
"/{bucketName:.+}/"
|
||||
},
|
||||
params = {
|
||||
DELETE
|
||||
},
|
||||
produces = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<DeleteResult> deleteObjects(
|
||||
@PathVariable String bucketName,
|
||||
@RequestBody Delete body) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
//return version id
|
||||
return ResponseEntity.ok(objectService.deleteObjects(bucketName, body));
|
||||
}
|
||||
|
||||
//================================================================================================
|
||||
// /{bucketName:.+}/{key}
|
||||
//================================================================================================
|
||||
|
||||
/**
|
||||
* Retrieves metadata from an object without returning the object itself.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName name of the bucket to look in
|
||||
* @return 200 with object metadata headers, 404 if not found.
|
||||
*/
|
||||
@RequestMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
method = RequestMethod.HEAD
|
||||
)
|
||||
public ResponseEntity<Void> headObject(@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key,
|
||||
@RequestHeader(value = IF_MATCH, required = false) List<String> match,
|
||||
@RequestHeader(value = IF_NONE_MATCH, required = false) List<String> noneMatch) {
|
||||
//TODO: needs modified-since handling, see API
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
|
||||
var s3ObjectMetadata = objectService.verifyObjectExists(bucketName, key.getKey());
|
||||
//return version id
|
||||
|
||||
if (s3ObjectMetadata != null) {
|
||||
objectService.verifyObjectMatching(match, noneMatch, s3ObjectMetadata);
|
||||
return ResponseEntity.ok()
|
||||
.eTag(s3ObjectMetadata.getEtag())
|
||||
.header(HttpHeaders.ACCEPT_RANGES, RANGES_BYTES)
|
||||
.headers(headers -> headers.setAll(s3ObjectMetadata.getStoreHeaders()))
|
||||
.headers(headers -> headers.setAll(userMetadataHeadersFrom(s3ObjectMetadata)))
|
||||
.headers(headers -> headers.setAll(s3ObjectMetadata.getEncryptionHeaders()))
|
||||
.headers(h -> h.setAll(checksumHeaderFrom(s3ObjectMetadata)))
|
||||
.header(X_AMZ_STORAGE_CLASS, s3ObjectMetadata.getStorageClass().toString())
|
||||
.lastModified(s3ObjectMetadata.getLastModified())
|
||||
.contentLength(Long.parseLong(s3ObjectMetadata.getSize()))
|
||||
.contentType(mediaTypeFrom(s3ObjectMetadata.getContentType()))
|
||||
.build();
|
||||
} else {
|
||||
return ResponseEntity.status(NOT_FOUND).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The DELETE operation removes an object.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName name of bucket containing the object.
|
||||
* @return ResponseEntity with Status Code 204 if object was successfully deleted.
|
||||
*/
|
||||
@DeleteMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
params = {
|
||||
NOT_LIFECYCLE
|
||||
}
|
||||
)
|
||||
public ResponseEntity<Void> deleteObject(@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
|
||||
var deleted = objectService.deleteObject(bucketName, key.getKey());
|
||||
|
||||
//return version id
|
||||
return ResponseEntity.noContent()
|
||||
.header(X_AMZ_DELETE_MARKER, String.valueOf(deleted))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the File identified by bucketName and fileName.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName The Bucket's name
|
||||
* @param range byte range
|
||||
*/
|
||||
@GetMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
params = {
|
||||
NOT_UPLOADS,
|
||||
NOT_UPLOAD_ID,
|
||||
NOT_TAGGING,
|
||||
NOT_LEGAL_HOLD,
|
||||
NOT_RETENTION,
|
||||
NOT_ACL,
|
||||
NOT_ATTRIBUTES
|
||||
}
|
||||
)
|
||||
public ResponseEntity<StreamingResponseBody> getObject(@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key,
|
||||
@RequestHeader(value = RANGE, required = false) HttpRange range,
|
||||
@RequestHeader(value = IF_MATCH, required = false) List<String> match,
|
||||
@RequestHeader(value = IF_NONE_MATCH, required = false) List<String> noneMatch,
|
||||
@RequestParam Map<String, String> queryParams) {
|
||||
//TODO: needs modified-since handling, see API
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
|
||||
var s3ObjectMetadata = objectService.verifyObjectExists(bucketName, key.getKey());
|
||||
objectService.verifyObjectMatching(match, noneMatch, s3ObjectMetadata);
|
||||
|
||||
if (range != null) {
|
||||
return getObjectWithRange(range, s3ObjectMetadata);
|
||||
}
|
||||
|
||||
//return version id
|
||||
return ResponseEntity
|
||||
.ok()
|
||||
.eTag(s3ObjectMetadata.getEtag())
|
||||
.header(HttpHeaders.ACCEPT_RANGES, RANGES_BYTES)
|
||||
.headers(headers -> headers.setAll(s3ObjectMetadata.getStoreHeaders()))
|
||||
.headers(headers -> headers.setAll(userMetadataHeadersFrom(s3ObjectMetadata)))
|
||||
.headers(headers -> headers.setAll(s3ObjectMetadata.getEncryptionHeaders()))
|
||||
.headers(h -> h.setAll(checksumHeaderFrom(s3ObjectMetadata)))
|
||||
.header(X_AMZ_STORAGE_CLASS, s3ObjectMetadata.getStorageClass().toString())
|
||||
.lastModified(s3ObjectMetadata.getLastModified())
|
||||
.contentLength(Long.parseLong(s3ObjectMetadata.getSize()))
|
||||
.contentType(mediaTypeFrom(s3ObjectMetadata.getContentType()))
|
||||
.headers(headers -> headers.setAll(overrideHeadersFrom(queryParams)))
|
||||
.body(outputStream -> Files.copy(s3ObjectMetadata.getDataPath(), outputStream));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an ACL to an object.
|
||||
* This method accepts a String instead of the POJO. We need to use JAX-B annotations
|
||||
* instead of Jackson annotations because AWS decided to use xsi:type annotations in the XML
|
||||
* representation, which are not supported by Jackson.
|
||||
* It doesn't seem to be possible to use bot JAX-B and Jackson for (de-)serialization in parallel.
|
||||
* :-(
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectAcl.html">API Reference</a>
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html">API Reference</a>
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html#canned-acl">API Reference</a>
|
||||
*
|
||||
* @param bucketName the Bucket in which to store the file in.
|
||||
* @return {@link ResponseEntity} with Status Code and empty ETag.
|
||||
*/
|
||||
@PutMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
params = {
|
||||
ACL,
|
||||
},
|
||||
consumes = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<Void> putObjectAcl(@PathVariable final String bucketName,
|
||||
@PathVariable ObjectKey key,
|
||||
@RequestHeader(value = X_AMZ_ACL, required = false) ObjectCannedACL cannedAcl,
|
||||
@RequestBody(required = false) String body) throws XMLStreamException, JAXBException {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
objectService.verifyObjectExists(bucketName, key.getKey());
|
||||
AccessControlPolicy policy;
|
||||
if (body != null) {
|
||||
policy = XmlUtil.deserializeJaxb(body);
|
||||
} else if (cannedAcl != null) {
|
||||
policy = CannedAclUtil.policyForCannedAcl(cannedAcl);
|
||||
} else {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
objectService.setAcl(bucketName, key.getKey(), policy);
|
||||
return ResponseEntity
|
||||
.ok()
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets ACL of an object.
|
||||
* This method returns a String instead of the POJO. We need to use JAX-B annotations
|
||||
* instead of Jackson annotations because AWS decided to use xsi:type annotations in the XML
|
||||
* representation, which are not supported by Jackson.
|
||||
* It doesn't seem to be possible to use bot JAX-B and Jackson for (de-)serialization in parallel.
|
||||
* :-(
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAcl.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName the Bucket in which to store the file in.
|
||||
* @return {@link ResponseEntity} with Status Code and empty ETag.
|
||||
*/
|
||||
@GetMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
params = {
|
||||
ACL,
|
||||
},
|
||||
produces = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<String> getObjectAcl(@PathVariable final String bucketName,
|
||||
@PathVariable ObjectKey key) throws JAXBException {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
objectService.verifyObjectExists(bucketName, key.getKey());
|
||||
var acl = objectService.getAcl(bucketName, key.getKey());
|
||||
return ResponseEntity.ok(XmlUtil.serializeJaxb(acl));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tags identified by bucketName and fileName.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectTagging.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName The Bucket's name
|
||||
*/
|
||||
@GetMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
params = {
|
||||
TAGGING
|
||||
},
|
||||
produces = {
|
||||
APPLICATION_XML_VALUE,
|
||||
APPLICATION_XML_VALUE + ";charset=UTF-8"
|
||||
}
|
||||
)
|
||||
public ResponseEntity<Tagging> getObjectTagging(@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
|
||||
var s3ObjectMetadata = objectService.verifyObjectExists(bucketName, key.getKey());
|
||||
|
||||
//return version id
|
||||
return ResponseEntity
|
||||
.ok()
|
||||
.eTag(s3ObjectMetadata.getEtag())
|
||||
.lastModified(s3ObjectMetadata.getLastModified())
|
||||
.body(new Tagging(new TagSet(s3ObjectMetadata.getTags())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets tags for a file identified by bucketName and fileName.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectTagging.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName The Bucket's name
|
||||
* @param body Tagging object
|
||||
*/
|
||||
@PutMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
params = {
|
||||
TAGGING
|
||||
},
|
||||
consumes = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<Void> putObjectTagging(@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key,
|
||||
@RequestBody Tagging body) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
|
||||
var s3ObjectMetadata = objectService.verifyObjectExists(bucketName, key.getKey());
|
||||
objectService.setObjectTags(bucketName, key.getKey(), body.getTagSet().getTags());
|
||||
return ResponseEntity
|
||||
.ok()
|
||||
.eTag(s3ObjectMetadata.getEtag())
|
||||
.lastModified(s3ObjectMetadata.getLastModified())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the legal hold for an object.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectLegalHold.html">API Reference</a>
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName The Bucket's name
|
||||
*/
|
||||
@GetMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
params = {
|
||||
LEGAL_HOLD
|
||||
},
|
||||
produces = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<LegalHold> getLegalHold(@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
bucketService.verifyBucketObjectLockEnabled(bucketName);
|
||||
var s3ObjectMetadata = objectService.verifyObjectLockConfiguration(bucketName, key.getKey());
|
||||
|
||||
return ResponseEntity
|
||||
.ok()
|
||||
.body(s3ObjectMetadata.getLegalHold());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets legal hold for an object.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectLegalHold.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName The Bucket's name
|
||||
* @param body legal hold
|
||||
*/
|
||||
@PutMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
params = {
|
||||
LEGAL_HOLD
|
||||
},
|
||||
consumes = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<Void> putLegalHold(@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key,
|
||||
@RequestBody LegalHold body) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
bucketService.verifyBucketObjectLockEnabled(bucketName);
|
||||
|
||||
objectService.verifyObjectExists(bucketName, key.getKey());
|
||||
objectService.setLegalHold(bucketName, key.getKey(), body);
|
||||
return ResponseEntity
|
||||
.ok()
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the retention for an object.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectRetention.html">API Reference</a>
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName The Bucket's name
|
||||
*/
|
||||
@GetMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
params = {
|
||||
RETENTION
|
||||
},
|
||||
produces = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<Retention> getObjectRetention(@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
bucketService.verifyBucketObjectLockEnabled(bucketName);
|
||||
var s3ObjectMetadata = objectService.verifyObjectLockConfiguration(bucketName, key.getKey());
|
||||
|
||||
return ResponseEntity
|
||||
.ok()
|
||||
.body(s3ObjectMetadata.getRetention());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets retention for an object.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectRetention.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName The Bucket's name
|
||||
* @param body retention
|
||||
*/
|
||||
@PutMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
params = {
|
||||
RETENTION
|
||||
},
|
||||
consumes = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<Void> putObjectRetention(@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key,
|
||||
@RequestBody Retention body) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
bucketService.verifyBucketObjectLockEnabled(bucketName);
|
||||
|
||||
objectService.verifyObjectExists(bucketName, key.getKey());
|
||||
objectService.verifyRetention(body);
|
||||
objectService.setRetention(bucketName, key.getKey(), body);
|
||||
return ResponseEntity
|
||||
.ok()
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attributes for an object.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAttributes.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName The Bucket's name
|
||||
*/
|
||||
@GetMapping(
|
||||
value = "/{bucketName:[a-z0-9.-]+}/{key:.*}",
|
||||
params = {
|
||||
ATTRIBUTES
|
||||
},
|
||||
produces = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<GetObjectAttributesOutput> getObjectAttributes(
|
||||
@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key,
|
||||
@RequestHeader(value = IF_MATCH, required = false) List<String> match,
|
||||
@RequestHeader(value = IF_NONE_MATCH, required = false) List<String> noneMatch,
|
||||
@RequestHeader(value = X_AMZ_OBJECT_ATTRIBUTES) List<String> objectAttributes) {
|
||||
//TODO: needs modified-since handling, see API
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
|
||||
//this is for either an object request, or a parts request.
|
||||
|
||||
S3ObjectMetadata s3ObjectMetadata = objectService.verifyObjectExists(bucketName, key.getKey());
|
||||
objectService.verifyObjectMatching(match, noneMatch, s3ObjectMetadata);
|
||||
GetObjectAttributesOutput response = new GetObjectAttributesOutput(
|
||||
ObjectService.getChecksum(s3ObjectMetadata),
|
||||
objectAttributes.contains(ObjectAttributes.ETAG.toString())
|
||||
? s3ObjectMetadata.getEtag()
|
||||
: null,
|
||||
null, //parts not supported right now
|
||||
objectAttributes.contains(ObjectAttributes.OBJECT_SIZE.toString())
|
||||
? Long.parseLong(s3ObjectMetadata.getSize())
|
||||
: null,
|
||||
objectAttributes.contains(ObjectAttributes.STORAGE_CLASS.toString())
|
||||
? s3ObjectMetadata.getStorageClass()
|
||||
: null
|
||||
);
|
||||
|
||||
//return version id
|
||||
return ResponseEntity
|
||||
.ok()
|
||||
.lastModified(s3ObjectMetadata.getLastModified())
|
||||
.body(response);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds an object to a bucket.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName the Bucket in which to store the file in.
|
||||
* @return {@link ResponseEntity} with Status Code and empty ETag.
|
||||
*/
|
||||
@PutMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
params = {
|
||||
NOT_UPLOAD_ID,
|
||||
NOT_TAGGING,
|
||||
NOT_LEGAL_HOLD,
|
||||
NOT_RETENTION,
|
||||
NOT_ACL
|
||||
},
|
||||
headers = {
|
||||
NOT_X_AMZ_COPY_SOURCE
|
||||
}
|
||||
)
|
||||
public ResponseEntity<Void> putObject(@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key,
|
||||
@RequestHeader(name = X_AMZ_TAGGING, required = false) List<Tag> tags,
|
||||
@RequestHeader(value = CONTENT_TYPE, required = false) String contentType,
|
||||
@RequestHeader(value = CONTENT_MD5, required = false) String contentMd5,
|
||||
@RequestHeader(value = X_AMZ_CONTENT_SHA256, required = false) String sha256Header,
|
||||
@RequestHeader(value = X_AMZ_STORAGE_CLASS, required = false, defaultValue = "STANDARD")
|
||||
StorageClass storageClass,
|
||||
@RequestHeader HttpHeaders httpHeaders,
|
||||
InputStream inputStream) {
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
|
||||
var stream = objectService.verifyMd5(inputStream, contentMd5, sha256Header);
|
||||
//TODO: need to extract owner from headers
|
||||
var owner = Owner.DEFAULT_OWNER;
|
||||
var s3ObjectMetadata =
|
||||
objectService.putS3Object(bucketName,
|
||||
key.getKey(),
|
||||
mediaTypeFrom(contentType).toString(),
|
||||
storeHeadersFrom(httpHeaders),
|
||||
stream,
|
||||
isV4ChunkedWithSigningEnabled(sha256Header),
|
||||
userMetadataFrom(httpHeaders),
|
||||
encryptionHeadersFrom(httpHeaders),
|
||||
tags,
|
||||
checksumAlgorithmFrom(httpHeaders),
|
||||
checksumFrom(httpHeaders),
|
||||
owner,
|
||||
storageClass);
|
||||
|
||||
//return version id
|
||||
return ResponseEntity
|
||||
.ok()
|
||||
.headers(h -> h.setAll(checksumHeaderFrom(s3ObjectMetadata)))
|
||||
.headers(h -> h.setAll(s3ObjectMetadata.getEncryptionHeaders()))
|
||||
.lastModified(s3ObjectMetadata.getLastModified())
|
||||
.eTag(s3ObjectMetadata.getEtag())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies an object to another bucket.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html">API Reference</a>
|
||||
*
|
||||
* @param bucketName name of the destination bucket
|
||||
* @param copySource path to source object
|
||||
* @return {@link CopyObjectResult}
|
||||
*/
|
||||
@PutMapping(
|
||||
value = "/{bucketName:.+}/{key:.*}",
|
||||
headers = {
|
||||
X_AMZ_COPY_SOURCE
|
||||
},
|
||||
params = {
|
||||
NOT_UPLOAD_ID,
|
||||
NOT_TAGGING,
|
||||
NOT_LEGAL_HOLD,
|
||||
NOT_RETENTION,
|
||||
NOT_ACL
|
||||
},
|
||||
produces = APPLICATION_XML_VALUE
|
||||
)
|
||||
public ResponseEntity<CopyObjectResult> copyObject(@PathVariable String bucketName,
|
||||
@PathVariable ObjectKey key,
|
||||
@RequestHeader(value = X_AMZ_COPY_SOURCE) CopySource copySource,
|
||||
@RequestHeader(value = X_AMZ_METADATA_DIRECTIVE,
|
||||
defaultValue = METADATA_DIRECTIVE_COPY) MetadataDirective metadataDirective,
|
||||
@RequestHeader(value = X_AMZ_COPY_SOURCE_IF_MATCH, required = false) List<String> match,
|
||||
@RequestHeader(value = X_AMZ_COPY_SOURCE_IF_NONE_MATCH,
|
||||
required = false) List<String> noneMatch,
|
||||
@RequestHeader HttpHeaders httpHeaders) {
|
||||
//TODO: needs modified-since handling, see API
|
||||
|
||||
bucketService.verifyBucketExists(bucketName);
|
||||
var s3ObjectMetadata = objectService.verifyObjectExists(copySource.getBucket(), copySource.getKey());
|
||||
objectService.verifyObjectMatchingForCopy(match, noneMatch, s3ObjectMetadata);
|
||||
|
||||
Map<String, String> metadata = Collections.emptyMap();
|
||||
if (MetadataDirective.REPLACE == metadataDirective) {
|
||||
metadata = userMetadataFrom(httpHeaders);
|
||||
}
|
||||
|
||||
//TODO: this is potentially illegal on S3. S3 throws a 400:
|
||||
// "This copy request is illegal because it is trying to copy an object to itself without
|
||||
// changing the object's metadata, storage class, website redirect location or encryption
|
||||
// attributes."
|
||||
|
||||
var copyObjectResult = objectService.copyS3Object(copySource.getBucket(),
|
||||
copySource.getKey(),
|
||||
bucketName,
|
||||
key.getKey(),
|
||||
encryptionHeadersFrom(httpHeaders),
|
||||
metadata);
|
||||
|
||||
//return version id / copy source version id
|
||||
//return expiration
|
||||
|
||||
if (copyObjectResult == null) {
|
||||
return ResponseEntity
|
||||
.notFound()
|
||||
.headers(headers -> headers.setAll(s3ObjectMetadata.getEncryptionHeaders()))
|
||||
.build();
|
||||
}
|
||||
return ResponseEntity
|
||||
.ok()
|
||||
.headers(headers -> headers.setAll(s3ObjectMetadata.getEncryptionHeaders()))
|
||||
.body(copyObjectResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* supports range different range ends. e.g. if content has 100 bytes, the range request could be:
|
||||
* bytes=10-100, 10--1 and 10-200
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html">API Reference</a>
|
||||
*
|
||||
* @param range {@link String}
|
||||
* @param s3ObjectMetadata {@link S3ObjectMetadata}
|
||||
*/
|
||||
private ResponseEntity<StreamingResponseBody> getObjectWithRange(HttpRange range,
|
||||
S3ObjectMetadata s3ObjectMetadata) {
|
||||
var fileSize = s3ObjectMetadata.getDataPath().toFile().length();
|
||||
var bytesToRead = Math.min(fileSize - 1, range.getRangeEnd(fileSize))
|
||||
- range.getRangeStart(fileSize) + 1;
|
||||
|
||||
if (bytesToRead < 0 || fileSize < range.getRangeStart(fileSize)) {
|
||||
return ResponseEntity.status(REQUESTED_RANGE_NOT_SATISFIABLE.value()).build();
|
||||
}
|
||||
|
||||
return ResponseEntity
|
||||
.status(PARTIAL_CONTENT.value())
|
||||
.headers(headers -> headers.setAll(userMetadataHeadersFrom(s3ObjectMetadata)))
|
||||
.headers(headers -> headers.setAll(s3ObjectMetadata.getStoreHeaders()))
|
||||
.headers(headers -> headers.setAll(s3ObjectMetadata.getEncryptionHeaders()))
|
||||
.header(HttpHeaders.ACCEPT_RANGES, RANGES_BYTES)
|
||||
.header(HttpHeaders.CONTENT_RANGE,
|
||||
String.format("bytes %s-%s/%s",
|
||||
range.getRangeStart(fileSize), bytesToRead + range.getRangeStart(fileSize) - 1,
|
||||
s3ObjectMetadata.getSize()))
|
||||
.eTag(s3ObjectMetadata.getEtag())
|
||||
.contentType(mediaTypeFrom(s3ObjectMetadata.getContentType()))
|
||||
.lastModified(s3ObjectMetadata.getLastModified())
|
||||
.contentLength(bytesToRead)
|
||||
.body(outputStream ->
|
||||
extractBytesToOutputStream(range, s3ObjectMetadata, outputStream, fileSize, bytesToRead)
|
||||
);
|
||||
}
|
||||
|
||||
private static void extractBytesToOutputStream(HttpRange range, S3ObjectMetadata s3ObjectMetadata,
|
||||
OutputStream outputStream, long fileSize, long bytesToRead) throws IOException {
|
||||
try (var fis = Files.newInputStream(s3ObjectMetadata.getDataPath())) {
|
||||
var skip = fis.skip(range.getRangeStart(fileSize));
|
||||
if (skip == range.getRangeStart(fileSize)) {
|
||||
IOUtils.copy(new BoundedInputStream(fis, bytesToRead), outputStream);
|
||||
} else {
|
||||
throw new IllegalStateException("Could not skip exact byte range");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock;
|
||||
|
||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
||||
import static org.springframework.http.HttpStatus.CONFLICT;
|
||||
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
||||
|
||||
import cc.iotkit.s3mock.dto.ErrorResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* {@link RuntimeException} to communicate general S3 errors.
|
||||
* These are handled by {@link S3MockConfiguration.S3MockExceptionHandler},
|
||||
* mapped to {@link ErrorResponse} and serialized.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html">API Reference</a>
|
||||
*/
|
||||
public class S3Exception extends RuntimeException {
|
||||
private static final String INVALID_REQUEST = "InvalidRequest";
|
||||
public static final S3Exception INVALID_PART_NUMBER =
|
||||
new S3Exception(BAD_REQUEST.value(), "InvalidArgument",
|
||||
"Part number must be an integer between 1 and 10000, inclusive");
|
||||
public static final S3Exception INVALID_PART = new S3Exception(BAD_REQUEST.value(), "InvalidPart",
|
||||
"One or more of the specified parts could not be found. The part might not have been "
|
||||
+ "uploaded, or the specified entity tagSet might not have matched the part's entity"
|
||||
+ " tagSet.");
|
||||
public static final S3Exception INVALID_PART_ORDER =
|
||||
new S3Exception(BAD_REQUEST.value(), "InvalidPartOrder",
|
||||
"The list of parts was not in ascending order. The parts list must be specified in "
|
||||
+ "order by part number.");
|
||||
public static final S3Exception NO_SUCH_UPLOAD_MULTIPART =
|
||||
new S3Exception(NOT_FOUND.value(), "NoSuchUpload",
|
||||
"The specified multipart upload does not exist. The upload ID might be invalid, or the "
|
||||
+ "multipart upload might have been aborted or completed.");
|
||||
public static final S3Exception ENTITY_TOO_SMALL =
|
||||
new S3Exception(BAD_REQUEST.value(), "EntityTooSmall",
|
||||
"Your proposed upload is smaller than the minimum allowed object size. "
|
||||
+ "Each part must be at least 5 MB in size, except the last part.");
|
||||
public static final S3Exception NO_SUCH_BUCKET =
|
||||
new S3Exception(NOT_FOUND.value(), "NoSuchBucket",
|
||||
"The specified bucket does not exist.");
|
||||
public static final S3Exception NO_SUCH_LIFECYCLE_CONFIGURATION =
|
||||
new S3Exception(NOT_FOUND.value(), "NoSuchLifecycleConfiguration",
|
||||
"The lifecycle configuration does not exist.");
|
||||
public static final S3Exception NO_SUCH_KEY =
|
||||
new S3Exception(NOT_FOUND.value(), "NoSuchKey", "The specified key does not exist.");
|
||||
public static final S3Exception NOT_MODIFIED =
|
||||
new S3Exception(HttpStatus.NOT_MODIFIED.value(), "NotModified", "Not Modified");
|
||||
public static final S3Exception PRECONDITION_FAILED =
|
||||
new S3Exception(HttpStatus.PRECONDITION_FAILED.value(),
|
||||
"PreconditionFailed", "At least one of the pre-conditions you specified did not hold");
|
||||
public static final S3Exception BUCKET_NOT_EMPTY =
|
||||
new S3Exception(CONFLICT.value(), "BucketNotEmpty",
|
||||
"The bucket you tried to delete is not empty.");
|
||||
public static final S3Exception INVALID_BUCKET_NAME =
|
||||
new S3Exception(BAD_REQUEST.value(), "InvalidBucketName",
|
||||
"The specified bucket is not valid.");
|
||||
public static final S3Exception BUCKET_ALREADY_EXISTS =
|
||||
new S3Exception(CONFLICT.value(), "BucketAlreadyExists",
|
||||
"The requested bucket name is not available. "
|
||||
+ "The bucket namespace is shared by all users of the system. "
|
||||
+ "Please select a different name and try again.");
|
||||
public static final S3Exception BUCKET_ALREADY_OWNED_BY_YOU =
|
||||
new S3Exception(CONFLICT.value(), "BucketAlreadyOwnedByYou",
|
||||
"Your previous request to create the named bucket succeeded and you already own it.");
|
||||
public static final S3Exception NOT_FOUND_BUCKET_OBJECT_LOCK =
|
||||
new S3Exception(BAD_REQUEST.value(), INVALID_REQUEST,
|
||||
"Bucket is missing Object Lock Configuration");
|
||||
public static final S3Exception NOT_FOUND_OBJECT_LOCK =
|
||||
new S3Exception(NOT_FOUND.value(), "NotFound",
|
||||
"The specified object does not have a ObjectLock configuration");
|
||||
public static final S3Exception INVALID_REQUEST_RETAINDATE =
|
||||
new S3Exception(BAD_REQUEST.value(), INVALID_REQUEST,
|
||||
"The retain until date must be in the future!");
|
||||
public static final S3Exception INVALID_REQUEST_MAXKEYS =
|
||||
new S3Exception(BAD_REQUEST.value(), INVALID_REQUEST,
|
||||
"maxKeys should be non-negative");
|
||||
public static final S3Exception INVALID_REQUEST_ENCODINGTYPE =
|
||||
new S3Exception(BAD_REQUEST.value(), INVALID_REQUEST,
|
||||
"encodingtype can only be none or 'url'");
|
||||
|
||||
public static final S3Exception BAD_REQUEST_MD5 =
|
||||
new S3Exception(BAD_REQUEST.value(), "BadRequest",
|
||||
"Content-MD5 does not match object md5");
|
||||
public static final S3Exception BAD_REQUEST_CONTENT =
|
||||
new S3Exception(BAD_REQUEST.value(), "UnexpectedContent",
|
||||
"This request contains unsupported content.");
|
||||
private final int status;
|
||||
private final String code;
|
||||
private final String message;
|
||||
|
||||
/**
|
||||
* Creates a new S3Exception to be mapped as an {@link ErrorResponse}.
|
||||
*
|
||||
* @param status The Error Status.
|
||||
* @param code The Error Code.
|
||||
* @param message The Error Message.
|
||||
*/
|
||||
public S3Exception(final int status, final String code, final String message) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
|
||||
/**
|
||||
* File Store Application that mocks Amazon S3.
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@ComponentScan(excludeFilters = {
|
||||
/*
|
||||
* TypeFilter to exclude classes with annotations that inherit {@link Component} to be
|
||||
* instantiated automatically by Spring so that we can still manually instantiate them through
|
||||
* a @Configuration class.
|
||||
*/
|
||||
@ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*s3mock.*Controller")
|
||||
})
|
||||
public class S3MockApplication {
|
||||
|
||||
public static void main(final String[] args) {
|
||||
SpringApplication.run(S3MockApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Copyright 2017-2024 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock;
|
||||
|
||||
import cc.iotkit.s3mock.dto.ErrorResponse;
|
||||
import cc.iotkit.s3mock.service.BucketService;
|
||||
import cc.iotkit.s3mock.service.MultipartService;
|
||||
import cc.iotkit.s3mock.service.ObjectService;
|
||||
import cc.iotkit.s3mock.store.KmsKeyStore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.filter.OrderedFormContentFilter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.filter.CommonsRequestLoggingFilter;
|
||||
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(S3MockProperties.class)
|
||||
public class S3MockConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Bean
|
||||
Filter kmsFilter(final KmsKeyStore kmsKeyStore,
|
||||
MappingJackson2XmlHttpMessageConverter messageConverter) {
|
||||
return new KmsValidationFilter(kmsKeyStore, messageConverter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) {
|
||||
configurer
|
||||
.defaultContentType(MediaType.APPLICATION_JSON, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_XML);
|
||||
configurer.mediaType("json", MediaType.APPLICATION_JSON);
|
||||
configurer.mediaType("xml", MediaType.TEXT_XML);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Profile("debug")
|
||||
public CommonsRequestLoggingFilter logFilter() {
|
||||
var filter = new CommonsRequestLoggingFilter();
|
||||
filter.setIncludeQueryString(true);
|
||||
filter.setIncludePayload(true);
|
||||
filter.setMaxPayloadLength(10000);
|
||||
filter.setIncludeHeaders(true);
|
||||
filter.setAfterMessagePrefix("REQUEST DATA : ");
|
||||
return filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an HttpMessageConverter for XML.
|
||||
*
|
||||
* @return The configured {@link MappingJackson2XmlHttpMessageConverter}.
|
||||
*/
|
||||
@Bean
|
||||
MappingJackson2XmlHttpMessageConverter messageConverter() {
|
||||
var mediaTypes = new ArrayList<MediaType>();
|
||||
mediaTypes.add(MediaType.APPLICATION_XML);
|
||||
mediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
mediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
|
||||
|
||||
var xmlConverter = new MappingJackson2XmlHttpMessageConverter();
|
||||
xmlConverter.setSupportedMediaTypes(mediaTypes);
|
||||
|
||||
return xmlConverter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
OrderedFormContentFilter httpPutFormContentFilter() {
|
||||
return new OrderedFormContentFilter() {
|
||||
@Override
|
||||
protected boolean shouldNotFilter(@NonNull HttpServletRequest request) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
FaviconController faviconController() {
|
||||
return new FaviconController();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ObjectController fileStoreController(ObjectService objectService, BucketService bucketService) {
|
||||
return new ObjectController(bucketService, objectService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
BucketController bucketController(BucketService bucketService, S3MockProperties properties) {
|
||||
return new BucketController(bucketService, properties.getRegion());
|
||||
}
|
||||
|
||||
@Bean
|
||||
MultipartController multipartController(BucketService bucketService,
|
||||
ObjectService objectService, MultipartService multipartService) {
|
||||
return new MultipartController(bucketService, objectService, multipartService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
S3MockExceptionHandler s3MockExceptionHandler() {
|
||||
return new S3MockExceptionHandler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
IllegalStateExceptionHandler illegalStateExceptionHandler() {
|
||||
return new IllegalStateExceptionHandler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ObjectCannedAclHeaderConverter objectCannedAclHeaderConverter() {
|
||||
return new ObjectCannedAclHeaderConverter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
TaggingHeaderConverter taggingHeaderConverter() {
|
||||
return new TaggingHeaderConverter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
HttpRangeHeaderConverter httpRangeHeaderConverter() {
|
||||
return new HttpRangeHeaderConverter();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ResponseEntityExceptionHandler} dealing with {@link S3Exception}s; Serializes them to
|
||||
* response output as suitable ErrorResponses.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html">API Reference</a>
|
||||
*/
|
||||
@ControllerAdvice(basePackages = "cc.iotkit.s3mock")
|
||||
static class S3MockExceptionHandler extends ResponseEntityExceptionHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(S3MockExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* Handles the given {@link S3Exception}.
|
||||
*
|
||||
* @param s3Exception {@link S3Exception} to be handled.
|
||||
* @return A {@link ResponseEntity} representing the handled {@link S3Exception}.
|
||||
*/
|
||||
@ExceptionHandler(S3Exception.class)
|
||||
public ResponseEntity<ErrorResponse> handleS3Exception(final S3Exception s3Exception) {
|
||||
LOG.debug("Responding with status {}: {}", s3Exception.getStatus(), s3Exception.getMessage(),
|
||||
s3Exception);
|
||||
|
||||
var errorResponse = new ErrorResponse(
|
||||
s3Exception.getCode(),
|
||||
s3Exception.getMessage(),
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
var headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_XML);
|
||||
|
||||
return ResponseEntity.status(s3Exception.getStatus()).headers(headers).body(errorResponse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ResponseEntityExceptionHandler} dealing with {@link IllegalStateException}s.
|
||||
* Serializes them to response output as a 500 Internal Server Error {@link ErrorResponse}.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html">API Reference</a>
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_Error.html">API Reference</a>
|
||||
*/
|
||||
@ControllerAdvice(basePackages = "cc.iotkit.s3mock")
|
||||
static class IllegalStateExceptionHandler extends ResponseEntityExceptionHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(IllegalStateExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* Handles the given {@link IllegalStateException}.
|
||||
*
|
||||
* @param exception {@link IllegalStateException} to be handled.
|
||||
* @return A {@link ResponseEntity} representing the handled {@link IllegalStateException}.
|
||||
*/
|
||||
@ExceptionHandler(IllegalStateException.class)
|
||||
public ResponseEntity<ErrorResponse> handleS3Exception(IllegalStateException exception) {
|
||||
LOG.debug("Responding with status {}: {}", INTERNAL_SERVER_ERROR, exception.getMessage(),
|
||||
exception);
|
||||
|
||||
var errorResponse = new ErrorResponse(
|
||||
"InternalError",
|
||||
"We encountered an internal error. Please try again.",
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
var headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_XML);
|
||||
|
||||
return ResponseEntity.internalServerError().headers(headers).body(errorResponse);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
|
||||
@ConfigurationProperties("s3mock")
|
||||
@Data
|
||||
public class S3MockProperties {
|
||||
// Property name for passing the HTTPS port to use. Defaults to
|
||||
// {@value S3MockApplication#DEFAULT_HTTPS_PORT}. If set to
|
||||
// {@value S3MockApplication#RANDOM_PORT}, a random port will be chosen.
|
||||
int httpPort;
|
||||
|
||||
// Property name for passing the global context path to use.
|
||||
// Defaults to "".
|
||||
// For example if set to `s3-mock` all endpoints will be available at
|
||||
// `http://host:port/s3-mock` instead of `http://host:port/`
|
||||
String contextPath="";
|
||||
|
||||
// Region is S3Mock is supposed to mock.
|
||||
// Must be an official AWS region string like "us-east-1"
|
||||
Region region;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock;
|
||||
|
||||
import cc.iotkit.s3mock.dto.Tag;
|
||||
import cc.iotkit.s3mock.util.AwsHttpHeaders;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Converts values of the {@link AwsHttpHeaders#X_AMZ_TAGGING} which is sent by the Amazon client.
|
||||
* Example: x-amz-tagging: tag1=value1&tag2=value2
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-tagging.html">API Reference</a>
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html">API Reference</a>
|
||||
*/
|
||||
class TaggingHeaderConverter implements Converter<String, List<Tag>> {
|
||||
@Override
|
||||
@Nullable
|
||||
public List<Tag> convert(@NonNull String source) {
|
||||
var tags = new ArrayList<Tag>();
|
||||
String[] tagPairs = StringUtils.split(source, '&');
|
||||
for (String tag : tagPairs) {
|
||||
tags.add(new Tag(tag));
|
||||
}
|
||||
return tags.isEmpty() ? null : tags;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_AbortIncompleteMultipartUpload.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@JsonRootName("ListPartsResult")
|
||||
public class AbortIncompleteMultipartUpload {
|
||||
@JsonProperty("DaysAfterInitiation")
|
||||
private Integer daysAfterInitiation;
|
||||
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import jakarta.xml.bind.annotation.XmlAccessType;
|
||||
import jakarta.xml.bind.annotation.XmlAccessorType;
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlElementWrapper;
|
||||
import jakarta.xml.bind.annotation.XmlRootElement;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_AccessControlPolicy.html">API Reference</a>.
|
||||
* This POJO uses JAX-B annotations instead of Jackson annotations because AWS decided to use
|
||||
* xsi:type annotations in the XML representation, which are not supported by Jackson.
|
||||
* JAX-B currently does not support Java records, see https://github.com/jakartaee/jaxb-api/issues/183
|
||||
*/
|
||||
@XmlRootElement(name = "AccessControlPolicy")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class AccessControlPolicy {
|
||||
@XmlElement(name = "Owner")
|
||||
private Owner owner;
|
||||
|
||||
@XmlElement(name = "Grant")
|
||||
@XmlElementWrapper(name = "AccessControlList")
|
||||
private List<Grant> accessControlList;
|
||||
|
||||
public AccessControlPolicy() {
|
||||
// Jackson needs the default constructor for deserialization.
|
||||
}
|
||||
|
||||
public AccessControlPolicy(Owner owner, List<Grant> accessControlList) {
|
||||
this.owner = owner;
|
||||
this.accessControlList = accessControlList;
|
||||
}
|
||||
|
||||
public Owner getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
public void setOwner(Owner owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public List<Grant> getAccessControlList() {
|
||||
return accessControlList;
|
||||
}
|
||||
|
||||
public void setAccessControlList(List<Grant> accessControlList) {
|
||||
this.accessControlList = accessControlList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
AccessControlPolicy policy = (AccessControlPolicy) o;
|
||||
return Objects.equals(owner, policy.owner) && Objects.equals(
|
||||
accessControlList, policy.accessControlList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(owner, accessControlList);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import cc.iotkit.s3mock.store.BucketMetadata;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_Bucket.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("Bucket")
|
||||
public class Bucket {
|
||||
@JsonIgnore
|
||||
private Path path;
|
||||
@JsonProperty("Name")
|
||||
private String name;
|
||||
@JsonProperty("CreationDate")
|
||||
private String creationDate;
|
||||
|
||||
public static Bucket from(BucketMetadata bucketMetadata) {
|
||||
if (bucketMetadata == null) {
|
||||
return null;
|
||||
}
|
||||
return new Bucket(bucketMetadata.getPath(),
|
||||
bucketMetadata.getName(),
|
||||
bucketMetadata.getCreationDate());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_BucketLifecycleConfiguration.html">API Reference</a>.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_LifecycleConfiguration.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@JsonRootName("LifecycleConfiguration")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class BucketLifecycleConfiguration {
|
||||
@JsonProperty("Rule")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<LifecycleRule> rules;
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DTO representing a list of buckets.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_Bucket.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("Buckets")
|
||||
public class Buckets {
|
||||
@JsonProperty("Bucket")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<Bucket> buckets;
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_Checksum.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("Checksum")
|
||||
public class Checksum {
|
||||
@JsonProperty("ChecksumCRC32")
|
||||
private String checksumCRC32;
|
||||
@JsonProperty("ChecksumCRC32C")
|
||||
private String checksumCRC32C;
|
||||
@JsonProperty("ChecksumSHA1")
|
||||
private String checksumSHA1;
|
||||
@JsonProperty("ChecksumSHA256")
|
||||
private String checksumSHA256;
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
public enum ChecksumAlgorithm {
|
||||
CRC32("CRC32"),
|
||||
CRC32C("CRC32C"),
|
||||
SHA1("SHA1"),
|
||||
SHA256("SHA256");
|
||||
|
||||
private final String value;
|
||||
|
||||
@JsonCreator
|
||||
ChecksumAlgorithm(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static ChecksumAlgorithm fromString(String value) {
|
||||
value = value.toUpperCase();
|
||||
switch (value) {
|
||||
case "SHA256":
|
||||
return SHA256;
|
||||
case "SHA1":
|
||||
return SHA1;
|
||||
case "CRC32":
|
||||
return CRC32;
|
||||
case "CRC32C":
|
||||
return CRC32C;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonValue
|
||||
public String toString() {
|
||||
return String.valueOf(this.value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Request to complete multipart upload.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
@JsonRootName("CompleteMultipartUpload")
|
||||
public class CompleteMultipartUpload {
|
||||
@JsonProperty("Part")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<CompletedPart> parts;
|
||||
|
||||
public void addPart(CompletedPart part) {
|
||||
this.parts.add(part);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import static cc.iotkit.s3mock.util.EtagUtil.normalizeEtag;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Result to be returned when completing a multipart request.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("CompleteMultipartUploadResult")
|
||||
public class CompleteMultipartUploadResult {
|
||||
@JsonProperty("Location")
|
||||
private String location;
|
||||
@JsonProperty("Bucket")
|
||||
private String bucket;
|
||||
@JsonProperty("Key")
|
||||
private String key;
|
||||
@JsonProperty("ETag")
|
||||
private String etag;
|
||||
|
||||
public CompleteMultipartUploadResult() {
|
||||
etag = normalizeEtag(etag);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import static cc.iotkit.s3mock.util.EtagUtil.normalizeEtag;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompletedPart.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@JsonRootName("CompletedPart")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class CompletedPart {
|
||||
@JsonProperty("PartNumber")
|
||||
private Integer partNumber;
|
||||
@JsonProperty("ETag")
|
||||
private String etag;
|
||||
@JsonProperty("ChecksumCRC32")
|
||||
private String checksumCRC32;
|
||||
@JsonProperty("ChecksumCRC32C")
|
||||
private String checksumCRC32C;
|
||||
@JsonProperty("ChecksumSHA1")
|
||||
private String checksumSHA1;
|
||||
@JsonProperty("ChecksumSHA256")
|
||||
private String checksumSHA256;
|
||||
|
||||
public CompletedPart() {
|
||||
etag = normalizeEtag(etag);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import static cc.iotkit.s3mock.util.EtagUtil.normalizeEtag;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObjectResult.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("CopyObjectResult")
|
||||
public class CopyObjectResult {
|
||||
@JsonProperty("LastModified")
|
||||
private String lastModified;
|
||||
@JsonProperty("ETag")
|
||||
private String etag;
|
||||
|
||||
public CopyObjectResult() {
|
||||
etag = normalizeEtag(etag);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import static cc.iotkit.s3mock.util.EtagUtil.normalizeEtag;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyPartResult.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("CopyPartResult")
|
||||
public class CopyPartResult {
|
||||
@JsonProperty("LastModified")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC")
|
||||
private Date lastModified;
|
||||
@JsonProperty("ETag")
|
||||
private String etag;
|
||||
|
||||
public CopyPartResult() {
|
||||
etag = normalizeEtag(etag);
|
||||
}
|
||||
|
||||
public static CopyPartResult from(final Date date, final String etag) {
|
||||
return new CopyPartResult(date, etag);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import lombok.Data;
|
||||
import software.amazon.awssdk.utils.http.SdkHttpUtils;
|
||||
|
||||
/**
|
||||
* Represents a S3 Object referenced by Bucket and Key.
|
||||
*/
|
||||
@Data
|
||||
public class CopySource {
|
||||
private String bucket;
|
||||
private String key;
|
||||
public static final String DELIMITER = "/";
|
||||
|
||||
/**
|
||||
* Creates a {@link CopySource} expecting the given String represents the source as {@code
|
||||
* /{bucket}/{key}}.
|
||||
*
|
||||
* @param copySource The object references.
|
||||
* @throws IllegalArgumentException If {@code copySource} could not be parsed.
|
||||
* @throws NullPointerException If {@code copySource} is null.
|
||||
*/
|
||||
public CopySource(String copySource) {
|
||||
//inefficient duplicate parsing of incoming String, call to default constructor must be the
|
||||
//first statement...
|
||||
this.bucket = extractBucketAndKeyArray(SdkHttpUtils.urlDecode(copySource))[0];
|
||||
this.key = extractBucketAndKeyArray(SdkHttpUtils.urlDecode(copySource))[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* we need to decode here because Spring does not do the decoding for RequestHeaders as it does
|
||||
* for path parameters.
|
||||
*/
|
||||
private static String[] extractBucketAndKeyArray(final String copySource) {
|
||||
requireNonNull(copySource, "copySource == null");
|
||||
final String source = normalizeCopySource(copySource);
|
||||
final String[] bucketAndKey = source.split(DELIMITER, 2);
|
||||
|
||||
if (bucketAndKey.length != 2) {
|
||||
throw new IllegalArgumentException(
|
||||
"Expected a copySource as '/{bucket}/{key}' but got: " + copySource);
|
||||
}
|
||||
|
||||
return bucketAndKey;
|
||||
}
|
||||
|
||||
private static String normalizeCopySource(final String copySource) {
|
||||
if (copySource.startsWith("/")) {
|
||||
return copySource.substring(1);
|
||||
}
|
||||
return copySource;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_DefaultRetention.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class DefaultRetention {
|
||||
//TODO: setting days & years not allowed!
|
||||
@JsonProperty("Days")
|
||||
private Integer days;
|
||||
@JsonProperty("Years")
|
||||
private Integer years;
|
||||
@JsonProperty("Mode")
|
||||
private Mode mode;
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Request to initiate a batch delete request.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
@JsonRootName("Delete")
|
||||
public class Delete {
|
||||
@JsonProperty("Quiet")
|
||||
private boolean quiet;
|
||||
@JsonProperty("Object")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<S3ObjectIdentifier> objectsToDelete;
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteMarkerEntry.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class DeleteMarkerEntry {
|
||||
@JsonProperty("IsLatest")
|
||||
private Boolean isLatest;
|
||||
@JsonProperty("Key")
|
||||
private String key;
|
||||
@JsonProperty("LastModified")
|
||||
private String lastModified;
|
||||
@JsonProperty("Owner")
|
||||
private Owner owner;
|
||||
@JsonProperty("VersionId")
|
||||
private String versionId;
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Result to be returned after batch delete request.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("DeleteResult")
|
||||
public class DeleteResult {
|
||||
@JsonProperty("Deleted")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<DeletedS3Object> deletedObjects;
|
||||
@JsonProperty("Error")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<Error> errors;
|
||||
|
||||
public void addDeletedObject(DeletedS3Object deletedObject) {
|
||||
deletedObjects.add(deletedObject);
|
||||
}
|
||||
|
||||
public void addError(Error error) {
|
||||
errors.add(error);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeletedObject.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class DeletedS3Object{
|
||||
@JsonProperty("Key")
|
||||
private String key;
|
||||
@JsonProperty("VersionId")
|
||||
private String versionId;
|
||||
@JsonProperty("DeleteMarker")
|
||||
private Boolean deleteMarker;
|
||||
@JsonProperty("DeleteMarkerVersionId")
|
||||
private String deleteMarkerVersionId;
|
||||
|
||||
public static DeletedS3Object from(S3ObjectIdentifier s3ObjectIdentifier) {
|
||||
return new DeletedS3Object(
|
||||
s3ObjectIdentifier.getKey(),
|
||||
s3ObjectIdentifier.getVersionId(),
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_Error.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Error {
|
||||
@JsonProperty("Code")
|
||||
private String code;
|
||||
@JsonProperty("Key")
|
||||
private String key;
|
||||
@JsonProperty("Message")
|
||||
private String message;
|
||||
@JsonProperty("VersionId")
|
||||
private String versionId;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* A DTO which can be used as a response body if an error occurred.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("Error")
|
||||
public class ErrorResponse {
|
||||
@JsonProperty("Code")
|
||||
String code;
|
||||
@JsonProperty("Message")
|
||||
String message;
|
||||
@JsonProperty("Resource")
|
||||
String resource;
|
||||
@JsonProperty("RequestId")
|
||||
String requestId;
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2017-2024 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import static cc.iotkit.s3mock.util.EtagUtil.normalizeEtag;
|
||||
|
||||
import cc.iotkit.s3mock.store.S3ObjectMetadata;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("GetObjectAttributesOutput")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class GetObjectAttributesOutput {
|
||||
@JsonProperty("Checksum")
|
||||
private Checksum checksum;
|
||||
@JsonProperty("ETag")
|
||||
private String etag;
|
||||
@JsonProperty("ObjectParts")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<GetObjectAttributesParts> objectParts;
|
||||
@JsonProperty("ObjectSize")
|
||||
private Long objectSize;
|
||||
@JsonProperty("StorageClass")
|
||||
private StorageClass storageClass;
|
||||
|
||||
public GetObjectAttributesOutput() {
|
||||
etag = normalizeEtag(etag);
|
||||
}
|
||||
|
||||
GetObjectAttributesOutput from(S3ObjectMetadata metadata) {
|
||||
return new GetObjectAttributesOutput(null,
|
||||
metadata.getEtag(),
|
||||
null,
|
||||
Long.valueOf(metadata.getSize()),
|
||||
metadata.getStorageClass());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAttributesParts.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@JsonRootName("GetObjectAttributesParts")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class GetObjectAttributesParts {
|
||||
@JsonProperty("MaxParts")
|
||||
private int maxParts;
|
||||
@JsonProperty("IsTruncated")
|
||||
private boolean isTruncated;
|
||||
@JsonProperty("NextPartNumberMarker")
|
||||
private int nextPartNumberMarker;
|
||||
@JsonProperty("PartNumberMarker")
|
||||
private int partNumberMarker;
|
||||
@JsonProperty("TotalPartsCount")
|
||||
private int totalPartsCount;
|
||||
@JsonProperty("Parts")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<ObjectPart> parts;
|
||||
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import jakarta.xml.bind.annotation.XmlAccessType;
|
||||
import jakarta.xml.bind.annotation.XmlAccessorType;
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlRootElement;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_Grant.html">API Reference</a>.
|
||||
*/
|
||||
@XmlRootElement(name = "Grant")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class Grant {
|
||||
|
||||
@XmlElement(name = "Grantee")
|
||||
private Grantee grantee;
|
||||
|
||||
@XmlElement(name = "Permission")
|
||||
private Permission permission;
|
||||
|
||||
public Grant() {
|
||||
// Jackson needs the default constructor for deserialization.
|
||||
}
|
||||
|
||||
public Grant(Grantee grantee, Permission permission) {
|
||||
this.grantee = grantee;
|
||||
this.permission = permission;
|
||||
}
|
||||
|
||||
public Grantee getGrantee() {
|
||||
return grantee;
|
||||
}
|
||||
|
||||
public void setGrantee(Grantee grantee) {
|
||||
this.grantee = grantee;
|
||||
}
|
||||
|
||||
public Permission getPermission() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
public void setPermission(Permission permission) {
|
||||
this.permission = permission;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Grant grant = (Grant) o;
|
||||
return Objects.equals(grantee, grant.grantee) && permission == grant.permission;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(grantee, permission);
|
||||
}
|
||||
|
||||
public enum Permission {
|
||||
FULL_CONTROL("FULL_CONTROL"),
|
||||
WRITE("WRITE"),
|
||||
WRITE_ACP("WRITE_ACP"),
|
||||
READ("READ"),
|
||||
READ_ACP("READ_ACP");
|
||||
|
||||
private final String value;
|
||||
|
||||
@JsonCreator
|
||||
Permission(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonValue
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright 2017-2024 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import jakarta.xml.bind.annotation.XmlAccessType;
|
||||
import jakarta.xml.bind.annotation.XmlAccessorType;
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlRootElement;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_Grantee.html">API Reference</a>.
|
||||
*/
|
||||
@XmlRootElement(name = "Grantee")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public abstract class Grantee extends Owner {
|
||||
|
||||
@XmlElement(name = "EmailAddress")
|
||||
private String emailAddress;
|
||||
@XmlElement(name = "URI")
|
||||
private URI uri;
|
||||
|
||||
protected Grantee() {
|
||||
// Jackson needs the default constructor for deserialization.
|
||||
}
|
||||
|
||||
protected Grantee(String id, String displayName, String emailAddress, URI uri) {
|
||||
super(id, displayName);
|
||||
this.emailAddress = emailAddress;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public static Grantee from(Owner owner) {
|
||||
return new CanonicalUser(owner.getId(), owner.getDisplayName(), null, null);
|
||||
}
|
||||
|
||||
public String getEmailAddress() {
|
||||
return emailAddress;
|
||||
}
|
||||
|
||||
public void setEmailAddress(String emailAddress) {
|
||||
this.emailAddress = emailAddress;
|
||||
}
|
||||
|
||||
public URI getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public void setUri(URI uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if (!super.equals(o)) {
|
||||
return false;
|
||||
}
|
||||
Grantee grantee = (Grantee) o;
|
||||
return Objects.equals(emailAddress, grantee.emailAddress) && Objects.equals(
|
||||
uri, grantee.uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), emailAddress, uri);
|
||||
}
|
||||
|
||||
@XmlType(name = "CanonicalUser")
|
||||
public static class CanonicalUser extends Grantee {
|
||||
public CanonicalUser() {
|
||||
}
|
||||
|
||||
public CanonicalUser(String id, String displayName, String emailAddress, URI uri) {
|
||||
super(id, displayName, emailAddress, uri);
|
||||
}
|
||||
}
|
||||
|
||||
@XmlType(name = "Group")
|
||||
public static class Group extends Grantee {
|
||||
public Group() {
|
||||
}
|
||||
|
||||
public Group(String id, String displayName, String emailAddress, URI uri) {
|
||||
super(id, displayName, emailAddress, uri);
|
||||
}
|
||||
|
||||
public static final URI AUTHENTICATED_USERS_URI = URI.create("http://acs.amazonaws.com/groups/global/AuthenticatedUsers");
|
||||
public static final URI ALL_USERS_URI = URI.create("http://acs.amazonaws.com/groups/global/AllUsers");
|
||||
public static final URI LOG_DELIVERY_URI = URI.create("http://acs.amazonaws.com/groups/s3/LogDelivery");
|
||||
}
|
||||
|
||||
@XmlType(name = "AmazonCustomerByEmail")
|
||||
public static class AmazonCustomerByEmail extends Grantee {
|
||||
public AmazonCustomerByEmail() {
|
||||
}
|
||||
|
||||
public AmazonCustomerByEmail(String id, String displayName, String emailAddress,
|
||||
URI uri) {
|
||||
super(id, displayName, emailAddress, uri);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Result to be returned after multipart upload initiation.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateMultipartUpload.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("InitiateMultipartUploadResult")
|
||||
public class InitiateMultipartUploadResult {
|
||||
@JsonProperty("Bucket")
|
||||
private String bucketName;
|
||||
@JsonProperty("Key")
|
||||
private String fileName;
|
||||
@JsonProperty("UploadId")
|
||||
private String uploadId;
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import software.amazon.awssdk.utils.DateUtils;
|
||||
|
||||
public class InstantDeserializer extends JsonDeserializer<Instant> {
|
||||
@Override
|
||||
public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
var deserialized = p.readValueAs(String.class);
|
||||
return DateUtils.parseIso8601Date(deserialized);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import software.amazon.awssdk.utils.DateUtils;
|
||||
|
||||
public class InstantSerializer extends JsonSerializer<Instant> {
|
||||
@Override
|
||||
public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers)
|
||||
throws IOException {
|
||||
gen.writeString(DateUtils.formatIso8601Date(value));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectLegalHold.html#API_PutObjectLegalHold_RequestSyntax">API Reference</a>.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@JsonRootName("LegalHold")
|
||||
public class LegalHold {
|
||||
@JsonProperty("Status")
|
||||
private Status status;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectLegalHold.html#API_PutObjectLegalHold_RequestSyntax">API Reference</a>.
|
||||
*/
|
||||
public enum Status {
|
||||
ON("ON"),
|
||||
OFF("OFF");
|
||||
|
||||
private final String value;
|
||||
|
||||
@JsonCreator
|
||||
Status(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonValue
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_LifecycleExpiration.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@JsonRootName("LifecycleExpiration")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class LifecycleExpiration {
|
||||
@JsonProperty("Date")
|
||||
@JsonSerialize(using = InstantSerializer.class)
|
||||
@JsonDeserialize(using = InstantDeserializer.class)
|
||||
private Instant date;
|
||||
|
||||
@JsonProperty("Days")
|
||||
private Integer days;
|
||||
|
||||
@JsonProperty("ExpiredObjectDeleteMarker")
|
||||
private Boolean expiredObjectDeleteMarker;
|
||||
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_LifecycleRule.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@JsonRootName("LifecycleRule")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class LifecycleRule {
|
||||
@JsonProperty("AbortIncompleteMultipartUpload")
|
||||
private AbortIncompleteMultipartUpload abortIncompleteMultipartUpload;
|
||||
@JsonProperty("Expiration")
|
||||
private LifecycleExpiration expiration;
|
||||
@JsonProperty("Filter")
|
||||
private LifecycleRuleFilter filter;
|
||||
@JsonProperty("ID")
|
||||
private String id;
|
||||
@JsonProperty("NoncurrentVersionExpiration")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private NoncurrentVersionExpiration noncurrentVersionExpiration;
|
||||
@JsonProperty("NoncurrentVersionTransition")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<NoncurrentVersionTransition> noncurrentVersionTransitions;
|
||||
@JsonProperty("Status")
|
||||
private Status status;
|
||||
@JsonProperty("Transition")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<Transition> transitions;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_LifecycleRule.html">API Reference</a>.
|
||||
*/
|
||||
public enum Status {
|
||||
ENABLED("Enabled"),
|
||||
DISABLED("Disabled");
|
||||
|
||||
private final String value;
|
||||
|
||||
@JsonCreator
|
||||
Status(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonValue
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_LifecycleRuleAndOperator.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@JsonRootName("LifecycleRuleAndOperator")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class LifecycleRuleAndOperator {
|
||||
@JsonProperty("ObjectSizeGreaterThan")
|
||||
private Long objectSizeGreaterThan;
|
||||
@JsonProperty("ObjectSizeLessThan")
|
||||
private Long objectSizeLessThan;
|
||||
@JsonProperty("Prefix")
|
||||
private String prefix;
|
||||
@JsonProperty("Tags")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<Tag> tags;
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_LifecycleRuleFilter.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@JsonRootName("LifecycleRuleFilter")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class LifecycleRuleFilter {
|
||||
@JsonProperty("ObjectSizeGreaterThan")
|
||||
private Long objectSizeGreaterThan;
|
||||
@JsonProperty("ObjectSizeLessThan")
|
||||
private Long objectSizeLessThan;
|
||||
@JsonProperty("Prefix")
|
||||
private String prefix;
|
||||
@JsonProperty("Tags")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<Tag> tags;
|
||||
@JsonProperty("And")
|
||||
private LifecycleRuleAndOperator and;
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Represents a result of listing all Buckets.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListBuckets.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("ListAllMyBucketsResult")
|
||||
public class ListAllMyBucketsResult {
|
||||
@JsonProperty("Owner")
|
||||
private Owner owner;
|
||||
@JsonProperty("Buckets")
|
||||
private Buckets buckets;
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a result of listing objects that reside in a Bucket.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjects.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("ListBucketResult")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class ListBucketResult {
|
||||
@JsonProperty("Name")
|
||||
private String name;
|
||||
@JsonProperty("Prefix")
|
||||
private String prefix;
|
||||
@JsonProperty("Marker")
|
||||
private String marker;
|
||||
@JsonProperty("MaxKeys")
|
||||
private int maxKeys;
|
||||
@JsonProperty("IsTruncated")
|
||||
private boolean isTruncated;
|
||||
@JsonProperty("EncodingType")
|
||||
private String encodingType;
|
||||
@JsonProperty("NextMarker")
|
||||
private String nextMarker;
|
||||
@JsonProperty("Contents")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<S3Object> contents;
|
||||
@JsonProperty("CommonPrefixes")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<Prefix> commonPrefixes;
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a result of listing objects that reside in a Bucket.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("ListBucketResult")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class ListBucketResultV2 {
|
||||
@JsonProperty("Name")
|
||||
private String name;
|
||||
@JsonProperty("Prefix")
|
||||
private String prefix;
|
||||
@JsonProperty("MaxKeys")
|
||||
private int maxKeys;
|
||||
@JsonProperty("IsTruncated")
|
||||
private boolean isTruncated;
|
||||
@JsonProperty("Contents")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<S3Object> contents;
|
||||
@JsonProperty("CommonPrefixes")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<Prefix> commonPrefixes;
|
||||
@JsonProperty("ContinuationToken")
|
||||
private String continuationToken;
|
||||
@JsonProperty("KeyCount")
|
||||
private String keyCount;
|
||||
@JsonProperty("NextContinuationToken")
|
||||
private String nextContinuationToken;
|
||||
@JsonProperty("StartAfter")
|
||||
private String startAfter;
|
||||
@JsonProperty("EncodingType")
|
||||
private String encodingType;
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* List Multipart Uploads result.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListMultipartUploads.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("ListMultipartUploadsResult")
|
||||
public class ListMultipartUploadsResult {
|
||||
@JsonProperty("Bucket")
|
||||
private String bucket;
|
||||
@JsonProperty("KeyMarker")
|
||||
private String keyMarker;
|
||||
@JsonProperty("Delimiter")
|
||||
private String delimiter;
|
||||
@JsonProperty("Prefix")
|
||||
private String prefix;
|
||||
@JsonProperty("UploadIdMarker")
|
||||
private String uploadIdMarker;
|
||||
@JsonProperty("MaxUploads")
|
||||
private int maxUploads;
|
||||
@JsonProperty("IsTruncated")
|
||||
private boolean isTruncated;
|
||||
@JsonProperty("NextKeyMarker")
|
||||
private String nextKeyMarker;
|
||||
@JsonProperty("NextUploadIdMarker")
|
||||
private String nextUploadIdMarker;
|
||||
@JsonProperty("Upload")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<MultipartUpload> multipartUploads;
|
||||
@JsonProperty("CommonPrefixes")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<Prefix> commonPrefixes;
|
||||
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* List-Parts result with some hard-coded values as this is sufficient for now.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListParts.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("ListPartsResult")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class ListPartsResult {
|
||||
@JsonProperty("Bucket")
|
||||
private String bucket;
|
||||
@JsonProperty("Key")
|
||||
private String key;
|
||||
@JsonProperty("UploadId")
|
||||
private String uploadId;
|
||||
@JsonProperty("PartNumberMarker")
|
||||
private String partNumberMarker;
|
||||
@JsonProperty("NextPartNumberMarker")
|
||||
private String nextPartNumberMarker;
|
||||
@JsonProperty("IsTruncated")
|
||||
private boolean truncated;
|
||||
@JsonProperty("StorageClass")
|
||||
private StorageClass storageClass;
|
||||
@JsonProperty("Part")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<Part> parts;
|
||||
@JsonProperty("Owner")
|
||||
private Owner owner;
|
||||
@JsonProperty("Initiator")
|
||||
private Owner initiator;
|
||||
@JsonProperty("ChecksumAlgorithm")
|
||||
private ChecksumAlgorithm checksumAlgorithm;
|
||||
|
||||
public ListPartsResult() {
|
||||
this.partNumberMarker = this.partNumberMarker == null ? "0" : this.partNumberMarker;
|
||||
this.nextPartNumberMarker = this.nextPartNumberMarker == null ? "1" : this.nextPartNumberMarker;
|
||||
this.storageClass = this.storageClass == null ? StorageClass.STANDARD : this.storageClass;
|
||||
}
|
||||
|
||||
public ListPartsResult(String bucketName,
|
||||
String key,
|
||||
String uploadId,
|
||||
List<Part> parts) {
|
||||
this(bucketName, key, uploadId, null, null, false, null, parts,
|
||||
null, null, null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a result of listing object versions that reside in a Bucket.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectVersions.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("ListBucketResult")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class ListVersionsResult {
|
||||
@JsonProperty("Name")
|
||||
private String name;
|
||||
@JsonProperty("Prefix")
|
||||
private String prefix;
|
||||
@JsonProperty("MaxKeys")
|
||||
private int maxKeys;
|
||||
@JsonProperty("IsTruncated")
|
||||
private boolean isTruncated;
|
||||
@JsonProperty("CommonPrefixes")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<Prefix> commonPrefixes;
|
||||
@JsonProperty("Delimiter")
|
||||
private String delimiter;
|
||||
@JsonProperty("EncodingType")
|
||||
private String encodingType;
|
||||
@JsonProperty("KeyMarker")
|
||||
private String keyMarker;
|
||||
@JsonProperty("VersionIdMarker")
|
||||
private String versionIdMarker;
|
||||
@JsonProperty("NextKeyMarker")
|
||||
private String nextKeyMarker;
|
||||
@JsonProperty("NextVersionIdMarker")
|
||||
private String nextVersionIdMarker;
|
||||
@JsonProperty("Version")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<ObjectVersion> objectVersions;
|
||||
@JsonProperty("DeleteMarker")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<DeleteMarkerEntry> deleteMarkers;
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
|
||||
/**
|
||||
* Get Bucket location result.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("LocationConstraint")
|
||||
public class LocationConstraint {
|
||||
@JsonSerialize(using = RegionSerializer.class)
|
||||
@JacksonXmlText
|
||||
private Region region;
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_DefaultRetention.html">API Reference</a>.
|
||||
*/
|
||||
public enum Mode {
|
||||
|
||||
GOVERNANCE("GOVERNANCE"),
|
||||
COMPLIANCE("COMPLIANCE");
|
||||
|
||||
private final String value;
|
||||
|
||||
@JsonCreator
|
||||
Mode(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonValue
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Container for elements related to a particular multipart upload.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_MultipartUpload.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class MultipartUpload {
|
||||
@JsonProperty("Key")
|
||||
private String key;
|
||||
@JsonProperty("UploadId")
|
||||
private String uploadId;
|
||||
@JsonProperty("Owner")
|
||||
private Owner owner;
|
||||
@JsonProperty("Initiator")
|
||||
private Owner initiator;
|
||||
@JsonProperty("StorageClass")
|
||||
private StorageClass storageClass;
|
||||
@JsonProperty("Initiated")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC")
|
||||
private Date initiated;
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_NoncurrentVersionExpiration.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@JsonRootName("NoncurrentVersionExpiration")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class NoncurrentVersionExpiration {
|
||||
@JsonProperty("NewerNoncurrentVersions")
|
||||
private Integer newerNoncurrentVersions;
|
||||
@JsonProperty("NoncurrentDays")
|
||||
private Integer noncurrentDays;
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_NoncurrentVersionTransition.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@JsonRootName("NoncurrentVersionTransition")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class NoncurrentVersionTransition {
|
||||
|
||||
@JsonProperty("NewerNoncurrentVersions")
|
||||
private Integer newerNoncurrentVersions;
|
||||
@JsonProperty("NoncurrentDays")
|
||||
private Integer noncurrentDays;
|
||||
@JsonProperty("StorageClass")
|
||||
private StorageClass storageClass;
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAttributes.html">API Reference</a>.
|
||||
* See x-amz-object-attributes
|
||||
*/
|
||||
public enum ObjectAttributes {
|
||||
ETAG("ETag"),
|
||||
CHECKSUM("Checksum"),
|
||||
OBJECT_PARTS("ObjectParts"),
|
||||
STORAGE_CLASS("StorageClass"),
|
||||
OBJECT_SIZE("ObjectSize");
|
||||
|
||||
private final String value;
|
||||
|
||||
ObjectAttributes(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* Key request value object.
|
||||
* Removes the trailing slash extracted from paths by Spring.
|
||||
* Used in conjunction with the PathVariable extracted by using "{key}" in the path pattern.
|
||||
* See {@link org.springframework.web.util.pattern.PathPattern}
|
||||
* Example path pattern: "/{bucketName:.+}/{key}"
|
||||
* Example incoming path: "/my-bucket/prefix/before/my/key"
|
||||
* By declaring "{key}", Spring extracts the absolute path "/prefix/before/my/key", but in S3, all
|
||||
* keys within a bucket are relative to the bucket, in this example "prefix/before/my/key".
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class ObjectKey {
|
||||
|
||||
private String key;
|
||||
|
||||
public ObjectKey() {
|
||||
requireNonNull(key);
|
||||
if (key.startsWith("/")) {
|
||||
key = key.substring(1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ObjectLockConfiguration.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("ObjectLockConfiguration")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class ObjectLockConfiguration {
|
||||
@JsonProperty("ObjectLockEnabled")
|
||||
private ObjectLockEnabled objectLockEnabled;
|
||||
@JsonProperty("Rule")
|
||||
private ObjectLockRule objectLockRule;
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ObjectLockConfiguration.html">API Reference</a>.
|
||||
*/
|
||||
public enum ObjectLockEnabled {
|
||||
ENABLED("Enabled");
|
||||
|
||||
private final String value;
|
||||
|
||||
@JsonCreator
|
||||
ObjectLockEnabled(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonValue
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ObjectLockRule.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class ObjectLockRule {
|
||||
@JsonProperty("DefaultRetention")
|
||||
private DefaultRetention defaultRetention;
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ObjectPart.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@JsonRootName("ObjectPart")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class ObjectPart {
|
||||
@JsonProperty("ChecksumCRC32")
|
||||
private String checksumCRC32;
|
||||
@JsonProperty("ChecksumCRC32C")
|
||||
private String checksumCRC32C;
|
||||
@JsonProperty("ChecksumSHA1")
|
||||
private String checksumSHA1;
|
||||
@JsonProperty("ChecksumSHA256")
|
||||
private String checksumSHA256;
|
||||
@JsonProperty("Size")
|
||||
private Long size;
|
||||
@JsonProperty("PartNumber")
|
||||
private Integer partNumber;
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright 2017-2024 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import static cc.iotkit.s3mock.util.EtagUtil.normalizeEtag;
|
||||
|
||||
import cc.iotkit.s3mock.store.S3ObjectMetadata;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ObjectVersion.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class ObjectVersion {
|
||||
@JsonProperty("Key")
|
||||
private String key;
|
||||
@JsonProperty("LastModified")
|
||||
private String lastModified;
|
||||
@JsonProperty("ETag")
|
||||
private String etag;
|
||||
@JsonProperty("Size")
|
||||
private String size;
|
||||
@JsonProperty("StorageClass")
|
||||
private StorageClass storageClass;
|
||||
@JsonProperty("Owner")
|
||||
private Owner owner;
|
||||
@JsonProperty("ChecksumAlgorithm")
|
||||
private ChecksumAlgorithm checksumAlgorithm;
|
||||
@JsonProperty("IsLatest")
|
||||
private Boolean isLatest;
|
||||
@JsonProperty("VersionId")
|
||||
private String versionId;
|
||||
|
||||
|
||||
public ObjectVersion() {
|
||||
etag = normalizeEtag(etag);
|
||||
}
|
||||
|
||||
public static ObjectVersion from(S3ObjectMetadata s3ObjectMetadata) {
|
||||
return new ObjectVersion(s3ObjectMetadata.getKey(),
|
||||
s3ObjectMetadata.getModificationDate(),
|
||||
s3ObjectMetadata.getEtag(),
|
||||
s3ObjectMetadata.getSize(),
|
||||
s3ObjectMetadata.getStorageClass(),
|
||||
s3ObjectMetadata.getOwner(),
|
||||
s3ObjectMetadata.getChecksumAlgorithm(),
|
||||
true,
|
||||
"staticVersion");
|
||||
}
|
||||
|
||||
public static ObjectVersion from(S3Object s3Object) {
|
||||
return new ObjectVersion(s3Object.getKey(),
|
||||
s3Object.getLastModified(),
|
||||
s3Object.getEtag(),
|
||||
s3Object.getSize(),
|
||||
s3Object.getStorageClass(),
|
||||
s3Object.getOwner(),
|
||||
s3Object.getChecksumAlgorithm(),
|
||||
true,
|
||||
"staticVersion");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright 2017-2024 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.xml.bind.annotation.XmlAccessType;
|
||||
import jakarta.xml.bind.annotation.XmlAccessorType;
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlRootElement;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Owner of a Bucket.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_Owner.html">API Reference</a>
|
||||
*/
|
||||
@XmlRootElement(name = "Owner")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class Owner {
|
||||
|
||||
/**
|
||||
* Default owner in S3Mock until support for ownership is implemented.
|
||||
*/
|
||||
public static final Owner DEFAULT_OWNER =
|
||||
new Owner("79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be",
|
||||
"s3-mock-file-store");
|
||||
public static final Owner DEFAULT_OWNER_BUCKET =
|
||||
new Owner("79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2df",
|
||||
"s3-mock-file-store-bucket");
|
||||
|
||||
@XmlElement(name = "ID")
|
||||
@JsonProperty("ID")
|
||||
private String id;
|
||||
@XmlElement(name = "DisplayName")
|
||||
@JsonProperty("DisplayName")
|
||||
private String displayName;
|
||||
|
||||
public Owner() {
|
||||
// Jackson needs the default constructor for deserialization.
|
||||
}
|
||||
|
||||
public Owner(String id, String displayName) {
|
||||
this.id = id;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Owner owner = (Owner) o;
|
||||
return Objects.equals(id, owner.id) && Objects.equals(displayName,
|
||||
owner.displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, displayName);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import static cc.iotkit.s3mock.util.EtagUtil.normalizeEtag;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_Part.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class Part {
|
||||
@JsonProperty("PartNumber")
|
||||
private Integer partNumber;
|
||||
@JsonProperty("ETag")
|
||||
private String etag;
|
||||
@JsonProperty("LastModified")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC")
|
||||
private Date lastModified;
|
||||
@JsonProperty("Size")
|
||||
private Long size;
|
||||
|
||||
public Part() {
|
||||
etag = normalizeEtag(etag);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Prefix {
|
||||
@JsonProperty("Prefix")
|
||||
private String prefix;
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import java.io.IOException;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
|
||||
/**
|
||||
* Serialize AWS Region objects.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html#API_GetBucketLocation_ResponseSyntax">API Reference</a>
|
||||
*/
|
||||
public class RegionSerializer extends JsonSerializer<Region> {
|
||||
@Override
|
||||
public void serialize(Region value, JsonGenerator gen, SerializerProvider serializers)
|
||||
throws IOException {
|
||||
var regionString = value.id();
|
||||
//API doc says to return "null" for the us-east-1 region.
|
||||
if ("us-east-1".equals(regionString)) {
|
||||
gen.writeString("null");
|
||||
}
|
||||
gen.writeString(regionString);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_control_S3Retention.html">API Reference</a>.
|
||||
* For unknown reasons, the timestamps in the Retention are serialized in Nanoseconds instead of
|
||||
* Milliseconds, like everywhere else.
|
||||
*/
|
||||
@Data
|
||||
@JsonRootName("Retention")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class Retention {
|
||||
@JsonProperty("Mode")
|
||||
private Mode mode;
|
||||
@JsonProperty("RetainUntilDate")
|
||||
@JsonSerialize(using = InstantSerializer.class)
|
||||
@JsonDeserialize(using = InstantDeserializer.class)
|
||||
private Instant retainUntilDate;
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright 2017-2024 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import static cc.iotkit.s3mock.util.EtagUtil.normalizeEtag;
|
||||
|
||||
import cc.iotkit.s3mock.store.S3ObjectMetadata;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Class representing an Object on S3.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_Object.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class S3Object{
|
||||
@JsonProperty("Key")
|
||||
String key;
|
||||
@JsonProperty("LastModified")
|
||||
String lastModified;
|
||||
@JsonProperty("ETag")
|
||||
String etag;
|
||||
@JsonProperty("Size")
|
||||
String size;
|
||||
@JsonProperty("StorageClass")
|
||||
StorageClass storageClass;
|
||||
@JsonProperty("Owner")
|
||||
Owner owner;
|
||||
@JsonProperty("ChecksumAlgorithm")
|
||||
ChecksumAlgorithm checksumAlgorithm;
|
||||
|
||||
public S3Object() {
|
||||
etag = normalizeEtag(etag);
|
||||
}
|
||||
|
||||
public static S3Object from(S3ObjectMetadata s3ObjectMetadata) {
|
||||
return new S3Object(s3ObjectMetadata.getKey(),
|
||||
s3ObjectMetadata.getModificationDate(),
|
||||
s3ObjectMetadata.getEtag(),
|
||||
s3ObjectMetadata.getSize(),
|
||||
s3ObjectMetadata.getStorageClass(),
|
||||
s3ObjectMetadata.getOwner(),
|
||||
s3ObjectMetadata.getChecksumAlgorithm());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Object identifier used in many APIs.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ObjectIdentifier.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
public class S3ObjectIdentifier {
|
||||
@JsonProperty("Key")
|
||||
private String key;
|
||||
@JsonProperty("VersionId")
|
||||
private String versionId;
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/storage-class-intro.html">API Reference</a>.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_Object.html">API Reference</a>.
|
||||
*/
|
||||
public enum StorageClass {
|
||||
STANDARD("STANDARD"),
|
||||
REDUCED_REDUNDANCY("REDUCED_REDUNDANCY"),
|
||||
GLACIER("GLACIER"),
|
||||
STANDARD_IA("STANDARD_IA"),
|
||||
ONEZONE_IA("ONEZONE_IA"),
|
||||
INTELLIGENT_TIERING("INTELLIGENT_TIERING"),
|
||||
DEEP_ARCHIVE("DEEP_ARCHIVE"),
|
||||
OUTPOSTS("OUTPOSTS"),
|
||||
GLACIER_IR("GLACIER_IR");
|
||||
|
||||
private final String value;
|
||||
|
||||
@JsonCreator
|
||||
StorageClass(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonValue
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_Tag.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@JsonRootName("Tag")
|
||||
@JacksonXmlRootElement(localName = "Tag")
|
||||
public class Tag {
|
||||
@JsonProperty("Key")
|
||||
private String key;
|
||||
@JsonProperty("Value")
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* Constructor for Spring's automatic header conversion.
|
||||
*/
|
||||
public Tag(final String keyValuePair) {
|
||||
this.key = keyValuePair.split("=")[0];
|
||||
this.value = keyValuePair.split("=")[1];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TagSet {
|
||||
@JacksonXmlProperty(localName = "Tag")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
private List<Tag> tags;
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Result to be returned for GetObjectTagging.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-tagging.html">API Reference</a>
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_Tagging.html">API Reference</a>
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@JsonRootName("Tagging")
|
||||
public class Tagging {
|
||||
@JacksonXmlProperty(localName = "TagSet")
|
||||
private TagSet tagSet;
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_Transition.html">API Reference</a>.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/lifecycle-transition-general-considerations.html">API Reference</a>.
|
||||
*/
|
||||
@Data
|
||||
@JsonRootName("Transition")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
public class Transition {
|
||||
@JsonProperty("Date")
|
||||
@JsonSerialize(using = InstantSerializer.class)
|
||||
@JsonDeserialize(using = InstantDeserializer.class)
|
||||
private Instant date;
|
||||
@JsonProperty("Days")
|
||||
private Integer days;
|
||||
@JsonProperty("StorageClass")
|
||||
private StorageClass storageClass;
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This set the namespace for all JAX-B serialization / deserialization POJOs in this package.
|
||||
*/
|
||||
@XmlSchema(namespace = "http://s3.amazonaws.com/doc/2006-03-01/",
|
||||
xmlns = { @XmlNs(prefix = "", namespaceURI = "http://s3.amazonaws.com/doc/2006-03-01/") },
|
||||
elementFormDefault = XmlNsForm.QUALIFIED)
|
||||
package cc.iotkit.s3mock.dto;
|
||||
|
||||
import jakarta.xml.bind.annotation.XmlNs;
|
||||
import jakarta.xml.bind.annotation.XmlNsForm;
|
||||
import jakarta.xml.bind.annotation.XmlSchema;
|
|
@ -0,0 +1,412 @@
|
|||
/*
|
||||
* Copyright 2017-2023 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.service;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isEmpty;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
|
||||
import static software.amazon.awssdk.utils.http.SdkHttpUtils.urlEncodeIgnoreSlashes;
|
||||
|
||||
import cc.iotkit.s3mock.S3Exception;
|
||||
import cc.iotkit.s3mock.dto.*;
|
||||
import cc.iotkit.s3mock.dto.Bucket;
|
||||
import cc.iotkit.s3mock.dto.BucketLifecycleConfiguration;
|
||||
import cc.iotkit.s3mock.dto.Buckets;
|
||||
import cc.iotkit.s3mock.dto.ListAllMyBucketsResult;
|
||||
import cc.iotkit.s3mock.dto.ListBucketResult;
|
||||
import cc.iotkit.s3mock.dto.ListBucketResultV2;
|
||||
import cc.iotkit.s3mock.dto.ListVersionsResult;
|
||||
import cc.iotkit.s3mock.dto.ObjectLockConfiguration;
|
||||
import cc.iotkit.s3mock.dto.ObjectVersion;
|
||||
import cc.iotkit.s3mock.dto.Prefix;
|
||||
import cc.iotkit.s3mock.dto.S3Object;
|
||||
import cc.iotkit.s3mock.store.BucketStore;
|
||||
import cc.iotkit.s3mock.store.ObjectStore;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import software.amazon.awssdk.utils.http.SdkHttpUtils;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
public class BucketService {
|
||||
private final Map<String, String> listObjectsPagingStateCache = new ConcurrentHashMap<>();
|
||||
private final BucketStore bucketStore;
|
||||
private final ObjectStore objectStore;
|
||||
|
||||
public BucketService(BucketStore bucketStore, ObjectStore objectStore) {
|
||||
this.bucketStore = bucketStore;
|
||||
this.objectStore = objectStore;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// createBucket("bucket1", false);
|
||||
}
|
||||
|
||||
public boolean isBucketEmpty(String bucketName) {
|
||||
return bucketStore.isBucketEmpty(bucketName);
|
||||
}
|
||||
|
||||
public boolean doesBucketExist(String bucketName) {
|
||||
return bucketStore.doesBucketExist(bucketName);
|
||||
}
|
||||
|
||||
public ListAllMyBucketsResult listBuckets() {
|
||||
var buckets = bucketStore
|
||||
.listBuckets()
|
||||
.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(Bucket::from)
|
||||
.collect(Collectors.toList());
|
||||
return new ListAllMyBucketsResult(Owner.DEFAULT_OWNER, new Buckets(buckets));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a Bucket identified by its name.
|
||||
*
|
||||
* @param bucketName of the Bucket to be retrieved
|
||||
* @return the Bucket or null if not found
|
||||
*/
|
||||
public Bucket getBucket(String bucketName) {
|
||||
return Bucket.from(bucketStore.getBucketMetadata(bucketName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Bucket identified by its name.
|
||||
*
|
||||
* @param bucketName of the Bucket to be created
|
||||
* @return the Bucket
|
||||
*/
|
||||
public Bucket createBucket(String bucketName, boolean objectLockEnabled) {
|
||||
return Bucket.from(bucketStore.createBucket(bucketName, objectLockEnabled));
|
||||
}
|
||||
|
||||
public boolean deleteBucket(String bucketName) {
|
||||
return bucketStore.deleteBucket(bucketName);
|
||||
}
|
||||
|
||||
public void setObjectLockConfiguration(String bucketName, ObjectLockConfiguration configuration) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
bucketStore.storeObjectLockConfiguration(bucketMetadata, configuration);
|
||||
}
|
||||
|
||||
public ObjectLockConfiguration getObjectLockConfiguration(String bucketName) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var objectLockConfiguration = bucketMetadata.getObjectLockConfiguration();
|
||||
if (objectLockConfiguration != null) {
|
||||
return objectLockConfiguration;
|
||||
} else {
|
||||
throw S3Exception.NOT_FOUND_BUCKET_OBJECT_LOCK;
|
||||
}
|
||||
}
|
||||
|
||||
public void setBucketLifecycleConfiguration(String bucketName,
|
||||
BucketLifecycleConfiguration configuration) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
bucketStore.storeBucketLifecycleConfiguration(bucketMetadata, configuration);
|
||||
}
|
||||
|
||||
public void deleteBucketLifecycleConfiguration(String bucketName) {
|
||||
setBucketLifecycleConfiguration(bucketName, null);
|
||||
}
|
||||
|
||||
public BucketLifecycleConfiguration getBucketLifecycleConfiguration(String bucketName) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var configuration = bucketMetadata.getBucketLifecycleConfiguration();
|
||||
if (configuration != null) {
|
||||
return configuration;
|
||||
} else {
|
||||
throw S3Exception.NO_SUCH_LIFECYCLE_CONFIGURATION;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves S3Objects from a bucket.
|
||||
*
|
||||
* @param bucketName the Bucket in which to list the file(s) in.
|
||||
* @param prefix {@link String} object file name starts with
|
||||
* @return S3Objects found in bucket for the given prefix
|
||||
*/
|
||||
public List<S3Object> getS3Objects(String bucketName, String prefix) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var uuids = bucketStore.lookupKeysInBucket(prefix, bucketName);
|
||||
return uuids
|
||||
.stream()
|
||||
.map(uuid -> objectStore.getS3ObjectMetadata(bucketMetadata, uuid))
|
||||
.filter(Objects::nonNull)
|
||||
.map(S3Object::from)
|
||||
// List Objects results are expected to be sorted by key
|
||||
.sorted(Comparator.comparing(S3Object::getKey))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public ListVersionsResult listVersions(String bucketName,
|
||||
String prefix,
|
||||
String delimiter,
|
||||
String encodingType,
|
||||
String startAfter,
|
||||
Integer maxKeys,
|
||||
String continuationToken,
|
||||
String keyMarker,
|
||||
String versionIdMarker) {
|
||||
//first implementation with dummy versions, just list objects for now.
|
||||
ListBucketResultV2 result = listObjectsV2(bucketName, prefix, delimiter, encodingType,
|
||||
startAfter, maxKeys, continuationToken);
|
||||
|
||||
var versions = result.getContents().stream().map(ObjectVersion::from).collect(Collectors.toList());
|
||||
|
||||
return new ListVersionsResult(result.getName(),
|
||||
result.getPrefix(),
|
||||
result.getMaxKeys(),
|
||||
result.isTruncated(),
|
||||
result.getCommonPrefixes(),
|
||||
delimiter,
|
||||
result.getEncodingType(),
|
||||
result.getContinuationToken(),
|
||||
null,
|
||||
result.getNextContinuationToken(),
|
||||
null,
|
||||
versions,
|
||||
null);
|
||||
}
|
||||
|
||||
public ListBucketResultV2 listObjectsV2(String bucketName,
|
||||
String prefix,
|
||||
String delimiter,
|
||||
String encodingType,
|
||||
String startAfter,
|
||||
Integer maxKeys,
|
||||
String continuationToken) {
|
||||
|
||||
var contents = getS3Objects(bucketName, prefix);
|
||||
var nextContinuationToken = (String) null;
|
||||
var isTruncated = false;
|
||||
|
||||
/*
|
||||
Start-after is valid only in first request.
|
||||
If the response is truncated,
|
||||
you can specify this parameter along with the continuation-token parameter,
|
||||
and then Amazon S3 ignores this parameter.
|
||||
*/
|
||||
if (continuationToken != null) {
|
||||
var continueAfter = listObjectsPagingStateCache.get(continuationToken);
|
||||
contents = filterObjectsBy(contents, continueAfter);
|
||||
listObjectsPagingStateCache.remove(continuationToken);
|
||||
} else {
|
||||
contents = filterObjectsBy(contents, startAfter);
|
||||
}
|
||||
|
||||
var commonPrefixes = collapseCommonPrefixes(prefix, delimiter, contents);
|
||||
contents = filterObjectsBy(contents, commonPrefixes);
|
||||
|
||||
if (contents.size() > maxKeys) {
|
||||
isTruncated = true;
|
||||
nextContinuationToken = UUID.randomUUID().toString();
|
||||
contents = contents.subList(0, maxKeys);
|
||||
listObjectsPagingStateCache.put(nextContinuationToken,
|
||||
contents.get(maxKeys - 1).getKey());
|
||||
}
|
||||
|
||||
var returnPrefix = prefix;
|
||||
var returnStartAfter = startAfter;
|
||||
var returnCommonPrefixes = commonPrefixes;
|
||||
|
||||
if (Objects.equals("url", encodingType)) {
|
||||
contents = apply(contents, object -> new S3Object(urlEncodeIgnoreSlashes(object.getKey()),
|
||||
object.getLastModified(),
|
||||
object.getEtag(),
|
||||
object.getSize(),
|
||||
object.getStorageClass(),
|
||||
object.getOwner(),
|
||||
object.getChecksumAlgorithm()));
|
||||
returnPrefix = urlEncodeIgnoreSlashes(prefix);
|
||||
returnStartAfter = urlEncodeIgnoreSlashes(startAfter);
|
||||
returnCommonPrefixes = apply(commonPrefixes, SdkHttpUtils::urlEncodeIgnoreSlashes);
|
||||
}
|
||||
|
||||
return new ListBucketResultV2(bucketName, returnPrefix, maxKeys,
|
||||
isTruncated, contents, returnCommonPrefixes.stream().map(Prefix::new).collect(Collectors.toList()),
|
||||
continuationToken, String.valueOf(contents.size()),
|
||||
nextContinuationToken, returnStartAfter, encodingType);
|
||||
}
|
||||
|
||||
@Deprecated(since = "2.12.2", forRemoval = true)
|
||||
public ListBucketResult listObjectsV1(String bucketName, String prefix, String delimiter,
|
||||
String marker, String encodingType, Integer maxKeys) {
|
||||
|
||||
verifyMaxKeys(maxKeys);
|
||||
verifyEncodingType(encodingType);
|
||||
|
||||
var contents = getS3Objects(bucketName, prefix);
|
||||
contents = filterObjectsBy(contents, marker);
|
||||
|
||||
var isTruncated = false;
|
||||
var nextMarker = (String) null;
|
||||
|
||||
var commonPrefixes = collapseCommonPrefixes(prefix, delimiter, contents);
|
||||
contents = filterObjectsBy(contents, commonPrefixes);
|
||||
if (maxKeys < contents.size()) {
|
||||
contents = contents.subList(0, maxKeys);
|
||||
isTruncated = true;
|
||||
if (maxKeys > 0) {
|
||||
nextMarker = contents.get(maxKeys - 1).getKey();
|
||||
}
|
||||
}
|
||||
|
||||
var returnPrefix = prefix;
|
||||
var returnCommonPrefixes = commonPrefixes;
|
||||
|
||||
if (Objects.equals("url", encodingType)) {
|
||||
contents = apply(contents, object -> new S3Object(urlEncodeIgnoreSlashes(object.getKey()),
|
||||
object.getLastModified(),
|
||||
object.getEtag(),
|
||||
object.getSize(),
|
||||
object.getStorageClass(),
|
||||
object.getOwner(),
|
||||
object.getChecksumAlgorithm()));
|
||||
returnPrefix = urlEncodeIgnoreSlashes(prefix);
|
||||
returnCommonPrefixes = apply(commonPrefixes, SdkHttpUtils::urlEncodeIgnoreSlashes);
|
||||
}
|
||||
|
||||
return new ListBucketResult(bucketName, returnPrefix, marker, maxKeys, isTruncated,
|
||||
encodingType, nextMarker, contents,
|
||||
returnCommonPrefixes.stream().map(Prefix::new).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public void verifyBucketExists(String bucketName) {
|
||||
if (!bucketStore.doesBucketExist(bucketName)) {
|
||||
throw S3Exception.NO_SUCH_BUCKET;
|
||||
}
|
||||
}
|
||||
|
||||
public void verifyBucketObjectLockEnabled(String bucketName) {
|
||||
if (!bucketStore.isObjectLockEnabled(bucketName)) {
|
||||
throw S3Exception.NOT_FOUND_BUCKET_OBJECT_LOCK;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html">API Reference Bucket Naming</a>.
|
||||
*/
|
||||
public void verifyBucketNameIsAllowed(String bucketName) {
|
||||
if (!bucketName.matches("[a-z0-9.-]+")) {
|
||||
throw S3Exception.INVALID_BUCKET_NAME;
|
||||
}
|
||||
}
|
||||
|
||||
public void verifyBucketIsEmpty(String bucketName) {
|
||||
if (!bucketStore.isBucketEmpty(bucketName)) {
|
||||
throw S3Exception.BUCKET_NOT_EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
public void verifyBucketDoesNotExist(String bucketName) {
|
||||
if (bucketStore.doesBucketExist(bucketName)) {
|
||||
//currently, all buckets have the same owner in S3Mock. If the bucket exists, it's owned by
|
||||
//the owner that tries to create the bucket owns the existing bucket too.
|
||||
throw S3Exception.BUCKET_ALREADY_OWNED_BY_YOU;
|
||||
}
|
||||
}
|
||||
|
||||
public void verifyMaxKeys(Integer maxKeys) {
|
||||
if (maxKeys < 0) {
|
||||
throw S3Exception.INVALID_REQUEST_MAXKEYS;
|
||||
}
|
||||
}
|
||||
|
||||
public void verifyEncodingType(String encodingType) {
|
||||
if (isNotEmpty(encodingType) && !"url".equals(encodingType)) {
|
||||
throw S3Exception.INVALID_REQUEST_ENCODINGTYPE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Collapse all bucket elements with keys starting with some prefix up to the given delimiter into
|
||||
* one prefix entry. Collapsed elements are removed from the contents list.
|
||||
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html">API Reference</a>
|
||||
*
|
||||
* @param queryPrefix the key prefix as specified in the list request
|
||||
* @param delimiter the delimiter used to separate a prefix from the rest of the object name
|
||||
* @param s3Objects the list of objects to use for collapsing the prefixes
|
||||
*/
|
||||
static List<String> collapseCommonPrefixes(String queryPrefix, String delimiter,
|
||||
List<S3Object> s3Objects) {
|
||||
var commonPrefixes = new ArrayList<String>();
|
||||
if (isEmpty(delimiter)) {
|
||||
return commonPrefixes;
|
||||
}
|
||||
|
||||
var normalizedQueryPrefix = queryPrefix == null ? "" : queryPrefix;
|
||||
|
||||
for (var c : s3Objects) {
|
||||
var key = c.getKey();
|
||||
if (key.startsWith(normalizedQueryPrefix)) {
|
||||
int delimiterIndex = key.indexOf(delimiter, normalizedQueryPrefix.length());
|
||||
if (delimiterIndex > 0) {
|
||||
var commonPrefix = key.substring(0, delimiterIndex + delimiter.length());
|
||||
if (!commonPrefixes.contains(commonPrefix)) {
|
||||
commonPrefixes.add(commonPrefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return commonPrefixes;
|
||||
}
|
||||
|
||||
private static <T> List<T> apply(List<T> contents, UnaryOperator<T> extractor) {
|
||||
return contents
|
||||
.stream()
|
||||
.map(extractor)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
static List<S3Object> filterObjectsBy(List<S3Object> s3Objects,
|
||||
String startAfter) {
|
||||
if (isNotEmpty(startAfter)) {
|
||||
return s3Objects
|
||||
.stream()
|
||||
.filter(p -> p.getKey().compareTo(startAfter) > 0)
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
return s3Objects;
|
||||
}
|
||||
}
|
||||
|
||||
static List<S3Object> filterObjectsBy(List<S3Object> s3Objects,
|
||||
List<String> commonPrefixes) {
|
||||
if (commonPrefixes != null && !commonPrefixes.isEmpty()) {
|
||||
return s3Objects
|
||||
.stream()
|
||||
.filter(c -> commonPrefixes
|
||||
.stream()
|
||||
.noneMatch(p -> c.getKey().startsWith(p))
|
||||
)
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
return s3Objects;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,329 @@
|
|||
/*
|
||||
* Copyright 2017-2024 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.service;
|
||||
|
||||
import cc.iotkit.s3mock.S3Exception;
|
||||
import cc.iotkit.s3mock.dto.ChecksumAlgorithm;
|
||||
import cc.iotkit.s3mock.dto.CompleteMultipartUploadResult;
|
||||
import cc.iotkit.s3mock.dto.CompletedPart;
|
||||
import cc.iotkit.s3mock.dto.CopyPartResult;
|
||||
import cc.iotkit.s3mock.dto.InitiateMultipartUploadResult;
|
||||
import cc.iotkit.s3mock.dto.ListMultipartUploadsResult;
|
||||
import cc.iotkit.s3mock.dto.ListPartsResult;
|
||||
import cc.iotkit.s3mock.dto.Owner;
|
||||
import cc.iotkit.s3mock.dto.Part;
|
||||
import cc.iotkit.s3mock.dto.StorageClass;
|
||||
import cc.iotkit.s3mock.store.BucketStore;
|
||||
import cc.iotkit.s3mock.store.MultipartStore;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpRange;
|
||||
|
||||
public class MultipartService {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MultipartService.class);
|
||||
static final Long MINIMUM_PART_SIZE = 5L * 1024L * 1024L;
|
||||
private final BucketStore bucketStore;
|
||||
private final MultipartStore multipartStore;
|
||||
|
||||
public MultipartService(BucketStore bucketStore, MultipartStore multipartStore) {
|
||||
this.bucketStore = bucketStore;
|
||||
this.multipartStore = multipartStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a part of a multipart upload.
|
||||
*
|
||||
* @param bucketName in which to upload
|
||||
* @param key of the object to upload
|
||||
* @param uploadId id of the upload
|
||||
* @param partNumber number of the part to store
|
||||
* @param inputStream file data to be stored
|
||||
* @param useV4ChunkedWithSigningFormat If {@code true}, V4-style signing is enabled.
|
||||
* @return the md5 digest of this part
|
||||
*/
|
||||
public String putPart(String bucketName,
|
||||
String key,
|
||||
String uploadId,
|
||||
String partNumber,
|
||||
InputStream inputStream,
|
||||
boolean useV4ChunkedWithSigningFormat,
|
||||
Map<String, String> encryptionHeaders) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var uuid = bucketMetadata.getID(key);
|
||||
if (uuid == null) {
|
||||
return null;
|
||||
}
|
||||
return multipartStore.putPart(bucketMetadata, uuid, uploadId, partNumber, inputStream,
|
||||
useV4ChunkedWithSigningFormat, encryptionHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the range, define by from/to, from the S3 Object, identified by the given key to given
|
||||
* destination into the given bucket.
|
||||
*
|
||||
* @param bucketName The source Bucket.
|
||||
* @param key Identifies the S3 Object.
|
||||
* @param copyRange Byte range to copy. Optional.
|
||||
* @param partNumber The part to copy.
|
||||
* @param destinationBucket The Bucket the target object (will) reside in.
|
||||
* @param destinationKey The target object key.
|
||||
* @param uploadId id of the upload.
|
||||
* @return etag of the uploaded file.
|
||||
*/
|
||||
public CopyPartResult copyPart(String bucketName,
|
||||
String key,
|
||||
HttpRange copyRange,
|
||||
String partNumber,
|
||||
String destinationBucket,
|
||||
String destinationKey,
|
||||
String uploadId,
|
||||
Map<String, String> encryptionHeaders) {
|
||||
var sourceBucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var destinationBucketMetadata = bucketStore.getBucketMetadata(destinationBucket);
|
||||
var sourceId = sourceBucketMetadata.getID(key);
|
||||
if (sourceId == null) {
|
||||
return null;
|
||||
}
|
||||
// source must be copied to destination
|
||||
var destinationId = bucketStore.addToBucket(destinationKey, destinationBucket);
|
||||
try {
|
||||
var partEtag =
|
||||
multipartStore.copyPart(sourceBucketMetadata, sourceId, copyRange, partNumber,
|
||||
destinationBucketMetadata, destinationId, uploadId, encryptionHeaders);
|
||||
return CopyPartResult.from(new Date(), "\"" + partEtag + "\"");
|
||||
} catch (Exception e) {
|
||||
//something went wrong with writing the destination file, clean up ID from BucketStore.
|
||||
bucketStore.removeFromBucket(destinationKey, destinationBucket);
|
||||
throw new IllegalStateException(String.format(
|
||||
"Could not copy part. sourceBucket=%s, destinationBucket=%s, key=%s, sourceId=%s, "
|
||||
+ "destinationId=%s, uploadId=%s", sourceBucketMetadata, destinationBucketMetadata,
|
||||
key, sourceId, destinationId, uploadId
|
||||
), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all multipart upload parts.
|
||||
*
|
||||
* @param bucketName name of the bucket
|
||||
* @param key object key
|
||||
* @param uploadId upload identifier
|
||||
* @return List of Parts
|
||||
*/
|
||||
public ListPartsResult getMultipartUploadParts(String bucketName, String key, String uploadId) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var id = bucketMetadata.getID(key);
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
var parts = multipartStore.getMultipartUploadParts(bucketMetadata, id, uploadId);
|
||||
return new ListPartsResult(bucketName, key, uploadId, parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts the upload.
|
||||
*
|
||||
* @param bucketName to which was uploaded
|
||||
* @param key which was uploaded
|
||||
* @param uploadId of the upload
|
||||
*/
|
||||
public void abortMultipartUpload(String bucketName, String key, String uploadId) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var id = bucketMetadata.getID(key);
|
||||
try {
|
||||
multipartStore.abortMultipartUpload(bucketMetadata, id, uploadId);
|
||||
} finally {
|
||||
bucketStore.removeFromBucket(key, bucketName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes a Multipart Upload for the given ID.
|
||||
*
|
||||
* @param bucketName in which to upload.
|
||||
* @param key of the file to upload.
|
||||
* @param uploadId id of the upload.
|
||||
* @param parts to concatenate.
|
||||
* @param location the location link to embed in result
|
||||
* @return etag of the uploaded file.
|
||||
*/
|
||||
public CompleteMultipartUploadResult completeMultipartUpload(String bucketName, String key,
|
||||
String uploadId, List<CompletedPart> parts, Map<String, String> encryptionHeaders,
|
||||
String location) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var id = bucketMetadata.getID(key);
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var etag = multipartStore
|
||||
.completeMultipartUpload(bucketMetadata, key, id, uploadId, parts, encryptionHeaders);
|
||||
return new CompleteMultipartUploadResult(location, bucketName, key, etag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares everything to store an object uploaded as multipart upload.
|
||||
*
|
||||
* @param bucketName Bucket to upload object in
|
||||
* @param key object to upload
|
||||
* @param contentType the content type
|
||||
* @param storeHeaders various headers to store
|
||||
* @param uploadId id of the upload
|
||||
* @param owner owner of the upload
|
||||
* @param initiator initiator of the upload
|
||||
* @param userMetadata custom metadata
|
||||
* @return upload result
|
||||
*/
|
||||
public InitiateMultipartUploadResult prepareMultipartUpload(String bucketName,
|
||||
String key,
|
||||
String contentType,
|
||||
Map<String, String> storeHeaders,
|
||||
String uploadId,
|
||||
Owner owner,
|
||||
Owner initiator,
|
||||
Map<String, String> userMetadata,
|
||||
Map<String, String> encryptionHeaders,
|
||||
StorageClass storageClass,
|
||||
String checksum,
|
||||
ChecksumAlgorithm checksumAlgorithm) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var id = bucketStore.addToBucket(key, bucketName);
|
||||
|
||||
try {
|
||||
multipartStore.prepareMultipartUpload(bucketMetadata,
|
||||
key,
|
||||
id,
|
||||
contentType,
|
||||
storeHeaders,
|
||||
uploadId,
|
||||
owner,
|
||||
initiator,
|
||||
userMetadata,
|
||||
encryptionHeaders,
|
||||
storageClass,
|
||||
checksum,
|
||||
checksumAlgorithm);
|
||||
return new InitiateMultipartUploadResult(bucketName, key, uploadId);
|
||||
} catch (Exception e) {
|
||||
//something went wrong with writing the destination file, clean up ID from BucketStore.
|
||||
bucketStore.removeFromBucket(key, bucketName);
|
||||
throw new IllegalStateException(String.format(
|
||||
"Could prepare Multipart Upload. bucket=%s, key=%s, id=%s, uploadId=%s",
|
||||
bucketMetadata, key, id, uploadId
|
||||
), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all not-yet completed parts of multipart uploads in a bucket.
|
||||
*
|
||||
* @param bucketName the bucket to use as a filter
|
||||
* @param prefix the prefix use as a filter
|
||||
* @return the list of not-yet completed multipart uploads.
|
||||
*/
|
||||
public ListMultipartUploadsResult listMultipartUploads(String bucketName, String prefix) {
|
||||
var multipartUploads = multipartStore.listMultipartUploads(bucketName, prefix);
|
||||
|
||||
// the result contains all uploads, use some common value as default
|
||||
var maxUploads = Math.max(1000, multipartUploads.size());
|
||||
// delimiter / prefix search not supported
|
||||
return new ListMultipartUploadsResult(bucketName, null, null, prefix, null,
|
||||
maxUploads, false, null, null, multipartUploads,
|
||||
Collections.emptyList());
|
||||
}
|
||||
|
||||
public void verifyPartNumberLimits(String partNumberString) {
|
||||
int partNumber;
|
||||
try {
|
||||
partNumber = Integer.parseInt(partNumberString);
|
||||
if (partNumber < 1 || partNumber > 10000) {
|
||||
LOG.error("Multipart part number invalid. partNumber={}", partNumberString);
|
||||
throw S3Exception.INVALID_PART_NUMBER;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
LOG.error("Multipart part number invalid. partNumber={}", partNumberString, nfe);
|
||||
throw S3Exception.INVALID_PART_NUMBER;
|
||||
}
|
||||
}
|
||||
|
||||
public void verifyMultipartParts(String bucketName, String key,
|
||||
String uploadId, List<CompletedPart> requestedParts) throws S3Exception {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var id = bucketMetadata.getID(key);
|
||||
if (id == null) {
|
||||
//TODO: is this the correct error?
|
||||
throw S3Exception.INVALID_PART;
|
||||
}
|
||||
verifyMultipartParts(bucketName, id, uploadId);
|
||||
|
||||
var uploadedParts = multipartStore.getMultipartUploadParts(bucketMetadata, id, uploadId);
|
||||
var uploadedPartsMap =
|
||||
uploadedParts
|
||||
.stream()
|
||||
.collect(Collectors.toMap(Part::getPartNumber, Part::getEtag));
|
||||
|
||||
var prevPartNumber = 0;
|
||||
for (var part : requestedParts) {
|
||||
if (!uploadedPartsMap.containsKey(part.getPartNumber())
|
||||
|| !uploadedPartsMap.get(part.getPartNumber()).equals(part.getEtag())) {
|
||||
LOG.error("Multipart part not valid. bucket={}, id={}, uploadId={}, partNumber={}",
|
||||
bucketMetadata, id, uploadId, part.getPartNumber());
|
||||
throw S3Exception.INVALID_PART;
|
||||
}
|
||||
if (part.getPartNumber() < prevPartNumber) {
|
||||
LOG.error("Multipart parts order invalid. bucket={}, id={}, uploadId={}, partNumber={}",
|
||||
bucketMetadata, id, uploadId, part.getPartNumber());
|
||||
throw S3Exception.INVALID_PART_ORDER;
|
||||
}
|
||||
prevPartNumber = part.getPartNumber();
|
||||
}
|
||||
}
|
||||
|
||||
public void verifyMultipartParts(String bucketName, UUID id, String uploadId) throws S3Exception {
|
||||
verifyMultipartUploadExists(uploadId);
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var uploadedParts = multipartStore.getMultipartUploadParts(bucketMetadata, id, uploadId);
|
||||
if (!uploadedParts.isEmpty()) {
|
||||
for (int i = 0; i < uploadedParts.size() - 1; i++) {
|
||||
var part = uploadedParts.get(i);
|
||||
if (part.getSize() < MINIMUM_PART_SIZE) {
|
||||
LOG.error("Multipart part size too small. bucket={}, id={}, uploadId={}, size={}",
|
||||
bucketMetadata, id, uploadId, part.getSize());
|
||||
throw S3Exception.ENTITY_TOO_SMALL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void verifyMultipartUploadExists(String uploadId) throws S3Exception {
|
||||
try {
|
||||
multipartStore.getMultipartUpload(uploadId);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw S3Exception.NO_SUCH_UPLOAD_MULTIPART;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,351 @@
|
|||
/*
|
||||
* Copyright 2017-2024 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.service;
|
||||
|
||||
import static cc.iotkit.s3mock.util.HeaderUtil.isV4ChunkedWithSigningEnabled;
|
||||
|
||||
import cc.iotkit.s3mock.S3Exception;
|
||||
import cc.iotkit.s3mock.dto.Error;
|
||||
import cc.iotkit.s3mock.dto.AccessControlPolicy;
|
||||
import cc.iotkit.s3mock.dto.Checksum;
|
||||
import cc.iotkit.s3mock.dto.ChecksumAlgorithm;
|
||||
import cc.iotkit.s3mock.dto.CopyObjectResult;
|
||||
import cc.iotkit.s3mock.dto.Delete;
|
||||
import cc.iotkit.s3mock.dto.DeleteResult;
|
||||
import cc.iotkit.s3mock.dto.DeletedS3Object;
|
||||
import cc.iotkit.s3mock.dto.LegalHold;
|
||||
import cc.iotkit.s3mock.dto.Owner;
|
||||
import cc.iotkit.s3mock.dto.Retention;
|
||||
import cc.iotkit.s3mock.dto.StorageClass;
|
||||
import cc.iotkit.s3mock.dto.Tag;
|
||||
import cc.iotkit.s3mock.store.BucketStore;
|
||||
import cc.iotkit.s3mock.store.ObjectStore;
|
||||
import cc.iotkit.s3mock.store.S3ObjectMetadata;
|
||||
import cc.iotkit.s3mock.util.AwsChunkedDecodingInputStream;
|
||||
import cc.iotkit.s3mock.util.DigestUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ObjectService {
|
||||
static final String WILDCARD_ETAG = "\"*\"";
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ObjectService.class);
|
||||
private final BucketStore bucketStore;
|
||||
private final ObjectStore objectStore;
|
||||
|
||||
public ObjectService(BucketStore bucketStore, ObjectStore objectStore) {
|
||||
this.bucketStore = bucketStore;
|
||||
this.objectStore = objectStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies an object to another bucket and encrypted object.
|
||||
*
|
||||
* @param sourceBucketName bucket to copy from.
|
||||
* @param sourceKey object key to copy.
|
||||
* @param destinationBucketName destination bucket.
|
||||
* @param destinationKey destination object key.
|
||||
* @param userMetadata User metadata to store for destination object
|
||||
*
|
||||
* @return an {@link CopyObjectResult} or null if source couldn't be found.
|
||||
*/
|
||||
public CopyObjectResult copyS3Object(String sourceBucketName,
|
||||
String sourceKey,
|
||||
String destinationBucketName,
|
||||
String destinationKey,
|
||||
Map<String, String> encryptionHeaders,
|
||||
Map<String, String> userMetadata) {
|
||||
var sourceBucketMetadata = bucketStore.getBucketMetadata(sourceBucketName);
|
||||
var destinationBucketMetadata = bucketStore.getBucketMetadata(destinationBucketName);
|
||||
var sourceId = sourceBucketMetadata.getID(sourceKey);
|
||||
if (sourceId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// source and destination is the same, pretend we copied - S3 does the same.
|
||||
if (sourceKey.equals(destinationKey) && sourceBucketName.equals(destinationBucketName)) {
|
||||
return objectStore.pretendToCopyS3Object(sourceBucketMetadata, sourceId, userMetadata);
|
||||
}
|
||||
|
||||
// source must be copied to destination
|
||||
var destinationId = bucketStore.addToBucket(destinationKey, destinationBucketName);
|
||||
try {
|
||||
return objectStore.copyS3Object(sourceBucketMetadata, sourceId,
|
||||
destinationBucketMetadata, destinationId, destinationKey,
|
||||
encryptionHeaders, userMetadata);
|
||||
} catch (Exception e) {
|
||||
//something went wrong with writing the destination file, clean up ID from BucketStore.
|
||||
bucketStore.removeFromBucket(destinationKey, destinationBucketName);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores an object inside a Bucket.
|
||||
*
|
||||
* @param bucketName Bucket to store the object in.
|
||||
* @param key object key to be stored.
|
||||
* @param contentType The files Content Type.
|
||||
* @param storeHeaders various headers to store
|
||||
* @param dataStream The File as InputStream.
|
||||
* @param useV4ChunkedWithSigningFormat If {@code true}, V4-style signing is enabled.
|
||||
* @param userMetadata User metadata to store for this object, will be available for the
|
||||
* object with the key prefixed with "x-amz-meta-".
|
||||
*
|
||||
* @return {@link S3ObjectMetadata}.
|
||||
*/
|
||||
public S3ObjectMetadata putS3Object(String bucketName,
|
||||
String key,
|
||||
String contentType,
|
||||
Map<String, String> storeHeaders,
|
||||
InputStream dataStream,
|
||||
boolean useV4ChunkedWithSigningFormat,
|
||||
Map<String, String> userMetadata,
|
||||
Map<String, String> encryptionHeaders,
|
||||
List<Tag> tags,
|
||||
ChecksumAlgorithm checksumAlgorithm,
|
||||
String checksum,
|
||||
Owner owner,
|
||||
StorageClass storageClass) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var id = bucketMetadata.getID(key);
|
||||
if (id == null) {
|
||||
id = bucketStore.addToBucket(key, bucketName);
|
||||
}
|
||||
return objectStore.storeS3ObjectMetadata(bucketMetadata, id, key, contentType, storeHeaders,
|
||||
dataStream, useV4ChunkedWithSigningFormat, userMetadata, encryptionHeaders, null, tags,
|
||||
checksumAlgorithm, checksum, owner, storageClass);
|
||||
}
|
||||
|
||||
public DeleteResult deleteObjects(String bucketName, Delete delete) {
|
||||
var response = new DeleteResult(new ArrayList<>(), new ArrayList<>());
|
||||
for (var object : delete.getObjectsToDelete()) {
|
||||
try {
|
||||
// ignore result of delete object.
|
||||
deleteObject(bucketName, object.getKey());
|
||||
// add deleted object even if it does not exist S3 does the same.
|
||||
response.addDeletedObject(DeletedS3Object.from(object));
|
||||
} catch (IllegalStateException e) {
|
||||
response.addError(
|
||||
new Error("InternalError",
|
||||
object.getKey(),
|
||||
"We encountered an internal error. Please try again.",
|
||||
object.getVersionId()));
|
||||
LOG.error("Object could not be deleted!", e);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an object key from a bucket.
|
||||
*
|
||||
* @param bucketName bucket containing the object.
|
||||
* @param key object to be deleted.
|
||||
*
|
||||
* @return true if deletion succeeded.
|
||||
*/
|
||||
public boolean deleteObject(String bucketName, String key) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var id = bucketMetadata.getID(key);
|
||||
if (id == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (objectStore.deleteObject(bucketMetadata, id)) {
|
||||
return bucketStore.removeFromBucket(key, bucketName);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets tags for a given object.
|
||||
*
|
||||
* @param bucketName Bucket the object is stored in.
|
||||
* @param key object key to store tags for.
|
||||
* @param tags List of tagSet objects.
|
||||
*/
|
||||
public void setObjectTags(String bucketName, String key, List<Tag> tags) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var uuid = bucketMetadata.getID(key);
|
||||
objectStore.storeObjectTags(bucketMetadata, uuid, tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets LegalHold for a given object.
|
||||
*
|
||||
* @param bucketName Bucket the object is stored in.
|
||||
* @param key object key to store tags for.
|
||||
* @param legalHold the legal hold.
|
||||
*/
|
||||
public void setLegalHold(String bucketName, String key, LegalHold legalHold) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var uuid = bucketMetadata.getID(key);
|
||||
objectStore.storeLegalHold(bucketMetadata, uuid, legalHold);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets AccessControlPolicy for a given object.
|
||||
*
|
||||
* @param bucketName Bucket the object is stored in.
|
||||
* @param key object key to store tags for.
|
||||
* @param policy the ACL.
|
||||
*/
|
||||
public void setAcl(String bucketName, String key, AccessControlPolicy policy) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var uuid = bucketMetadata.getID(key);
|
||||
objectStore.storeAcl(bucketMetadata, uuid, policy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves AccessControlPolicy for a given object.
|
||||
*
|
||||
* @param bucketName Bucket the object is stored in.
|
||||
* @param key object key to store tags for.
|
||||
*/
|
||||
public AccessControlPolicy getAcl(String bucketName, String key) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var uuid = bucketMetadata.getID(key);
|
||||
return objectStore.readAcl(bucketMetadata, uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Retention for a given object.
|
||||
*
|
||||
* @param bucketName Bucket the object is stored in.
|
||||
* @param key object key to store tags for.
|
||||
* @param retention the retention.
|
||||
*/
|
||||
public void setRetention(String bucketName, String key, Retention retention) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var uuid = bucketMetadata.getID(key);
|
||||
objectStore.storeRetention(bucketMetadata, uuid, retention);
|
||||
}
|
||||
|
||||
public void verifyRetention(Retention retention) {
|
||||
var retainUntilDate = retention.getRetainUntilDate();
|
||||
if (Instant.now().isAfter(retainUntilDate)) {
|
||||
throw S3Exception.INVALID_REQUEST_RETAINDATE;
|
||||
}
|
||||
}
|
||||
|
||||
public InputStream verifyMd5(InputStream inputStream, String contentMd5,
|
||||
String sha256Header) {
|
||||
try {
|
||||
var tempFile = Files.createTempFile("md5Check", "");
|
||||
inputStream.transferTo(Files.newOutputStream(tempFile));
|
||||
|
||||
try (var stream = isV4ChunkedWithSigningEnabled(sha256Header)
|
||||
? new AwsChunkedDecodingInputStream(Files.newInputStream(tempFile))
|
||||
: Files.newInputStream(tempFile)) {
|
||||
verifyMd5(stream, contentMd5);
|
||||
return Files.newInputStream(tempFile);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw S3Exception.BAD_REQUEST_CONTENT;
|
||||
}
|
||||
}
|
||||
|
||||
public void verifyMd5(InputStream inputStream, String contentMd5) {
|
||||
if (contentMd5 != null) {
|
||||
var md5 = DigestUtil.base64Digest(inputStream);
|
||||
if (!md5.equals(contentMd5)) {
|
||||
LOG.error("Content-MD5 {} does not match object md5 {}", contentMd5, md5);
|
||||
throw S3Exception.BAD_REQUEST_MD5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FOr copy use-cases, we need to return PRECONDITION_FAILED only.
|
||||
*/
|
||||
public void verifyObjectMatchingForCopy(List<String> match, List<String> noneMatch,
|
||||
S3ObjectMetadata s3ObjectMetadata) {
|
||||
try {
|
||||
verifyObjectMatching(match, noneMatch, s3ObjectMetadata);
|
||||
} catch (S3Exception e) {
|
||||
if (S3Exception.NOT_MODIFIED.equals(e)) {
|
||||
throw S3Exception.PRECONDITION_FAILED;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void verifyObjectMatching(List<String> match, List<String> noneMatch,
|
||||
S3ObjectMetadata s3ObjectMetadata) {
|
||||
if (s3ObjectMetadata != null) {
|
||||
var etag = s3ObjectMetadata.getEtag();
|
||||
if (match != null) {
|
||||
if (match.contains(WILDCARD_ETAG)) {
|
||||
//request cares only that the object exists
|
||||
return;
|
||||
} else if (!match.contains(etag)) {
|
||||
throw S3Exception.PRECONDITION_FAILED;
|
||||
}
|
||||
}
|
||||
if (noneMatch != null && (noneMatch.contains(WILDCARD_ETAG) || noneMatch.contains(etag))) {
|
||||
//request cares only that the object DOES NOT exist.
|
||||
throw S3Exception.NOT_MODIFIED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public S3ObjectMetadata verifyObjectExists(String bucketName, String key) {
|
||||
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
|
||||
var uuid = bucketMetadata.getID(key);
|
||||
if (uuid == null) {
|
||||
throw S3Exception.NO_SUCH_KEY;
|
||||
}
|
||||
var s3ObjectMetadata = objectStore.getS3ObjectMetadata(bucketMetadata, uuid);
|
||||
if (s3ObjectMetadata == null) {
|
||||
throw S3Exception.NO_SUCH_KEY;
|
||||
}
|
||||
return s3ObjectMetadata;
|
||||
}
|
||||
|
||||
public S3ObjectMetadata verifyObjectLockConfiguration(String bucketName, String key) {
|
||||
var s3ObjectMetadata = verifyObjectExists(bucketName, key);
|
||||
var noLegalHold = s3ObjectMetadata.getLegalHold() == null;
|
||||
var noRetention = s3ObjectMetadata.getRetention() == null;
|
||||
if (noLegalHold && noRetention) {
|
||||
throw S3Exception.NOT_FOUND_OBJECT_LOCK;
|
||||
}
|
||||
return s3ObjectMetadata;
|
||||
}
|
||||
|
||||
public static Checksum getChecksum(S3ObjectMetadata s3ObjectMetadata) {
|
||||
ChecksumAlgorithm checksumAlgorithm = s3ObjectMetadata.getChecksumAlgorithm();
|
||||
if (checksumAlgorithm != null) {
|
||||
return new Checksum(
|
||||
checksumAlgorithm == ChecksumAlgorithm.CRC32 ? s3ObjectMetadata.getChecksum() : null,
|
||||
checksumAlgorithm == ChecksumAlgorithm.CRC32C ? s3ObjectMetadata.getChecksum() : null,
|
||||
checksumAlgorithm == ChecksumAlgorithm.SHA1 ? s3ObjectMetadata.getChecksum() : null,
|
||||
checksumAlgorithm == ChecksumAlgorithm.SHA256 ? s3ObjectMetadata.getChecksum() : null
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright 2017-2022 Adobe.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cc.iotkit.s3mock.service;
|
||||
|
||||
import cc.iotkit.s3mock.store.BucketStore;
|
||||
import cc.iotkit.s3mock.store.MultipartStore;
|
||||
import cc.iotkit.s3mock.store.ObjectStore;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class ServiceConfiguration {
|
||||
|
||||
@Bean
|
||||
BucketService bucketService(BucketStore bucketStore, ObjectStore objectStore) {
|
||||
return new BucketService(bucketStore, objectStore);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ObjectService objectService(BucketStore bucketStore, ObjectStore objectStore) {
|
||||
return new ObjectService(bucketStore, objectStore);
|
||||
}
|
||||
|
||||
@Bean
|
||||
MultipartService multipartService(BucketStore bucketStore, MultipartStore multipartStore) {
|
||||
return new MultipartService(bucketStore, multipartStore);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue