Java
AWS
spring
spring-boot

Spring Cloud AWSのResourceハンドリング機能でAWS S3リソースを検索する

More than 1 year has passed since last update.

AWSのSDKでS3上のリソースを操作できるが、リソース名を指定し検索してくれる機能がないようです。Spring Cloud AWSのResourceハンドリングを使えば、指定のAntパターンにマッチングするリソースの検索が可能です。

完成後画面のイメージ↓
2017-07-26_174655.jpg

検証環境

・Spring Boot(Thymeleaf)1.4.3

必要なライブラリ

pom.xml
    ...
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-aws-context</artifactId>
      <version>1.2.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-aws-autoconfigure</artifactId>
        <version>1.2.1.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>com.amazonaws</groupId>
      <artifactId>aws-java-sdk</artifactId>
      <version>1.11.163</version>
    </dependency>
    ...

AWS S3に接続するための設定

application.propertiesにアクセスキー、シークレットキー、リージョンを指定する。
また、ローカルからのアクセスであるため、リージョンの自動検知を「false」に指定する。

application.properties
cloud.aws.credentials.accessKey=AKIAXXXXXXXXXA3Q
cloud.aws.credentials.secretKey=T9JBXXXXXXXXXXXXXXXXXXXXGfLAQ
cloud.aws.region.static=ap-northeast-1
cloud.aws.region.auto=false

上記の設定値を読み込むために、AWSコンフィグクラスを作成する。

AWSConfig
@Configuration
public class AWSConfig {

    @Value("${cloud.aws.credentials.accessKey}")
    private String accessKey;

    @Value("${cloud.aws.credentials.secretKey}")
    private String secretKey;

    @Value("${cloud.aws.region.static}")
    private String region;

    @Bean
    public BasicAWSCredentials basicAWSCredentials() {
        return new BasicAWSCredentials(accessKey, secretKey);
    }

    @Bean
    public AmazonS3 amazonS3Client(AWSCredentials awsCredentials) {
        return AmazonS3ClientBuilder.standard().withRegion(Regions.fromName(region)).build();
    }
}

上記を作成することで、DIコンテナからResourceLoaderとAmazonS3のインスタンスを取得することが可能になります。

アプリケーションレイヤ

普通のControllerクラスと画面表示用のフォームクラスを作成する。

AwsS3Controller
    @RequestMapping(value="/aws/s3/search", method=RequestMethod.POST)
    public String search(Model model, S3FileForm fileForm) throws IOException {

        S3FileForm s3FileForm = storageService.search(fileForm.getFileName());
        model.addAttribute("s3FileForm", s3FileForm);

        return "/aws/s3/downloadFromS3";
    }
S3FileForm
public class S3FileForm implements Serializable {

    private String fileName;

    private String downloadKey;

    private String[] checkedItems;

    private List<S3File> fileList;

    //...get set省略
}
S3File
public class S3File implements Serializable {

    private String bucketName;

    private String key;

    private String contentType;

    private Date lastModifiedDate;

    //...get set省略
}

ドメインレイヤ

サービスクラスにてResourcePatternResolverを使って、Antパターンs3://cinpo/**/*testにマッチングするリソース名を検索し、対象リソース一覧を取得する。さらに、リソース名でAmazonS3オブジェクトを生成し、最終更新日などのキー情報を取得する。
s3://**/*.txtを使えばアクセス可能な全バケットを対象に検索が可能です。

S3StorageService
/**
 * AWS S3操作用サービス
 * @author
 *
 */
@Service
public class S3StorageService {

    private final String BUCKET_NAME = "cinpo";

    @Autowired
    private ResourceLoader resourceLoader;

    @Autowired
    private ResourcePatternResolver resourcePatternResolver;

    @Autowired
    private AmazonS3 amazonS3;

    /**
     * リソース検索
     * 
     * @param fileName
     */
    public S3FileForm search(String fileName) {

        // 画面表示用フォーム
        S3FileForm s3FileForm = new S3FileForm();

            Resource[] resources = null;
            try {
                // Antパターン(s3://cinpo/**/*test.*)を指定し、対象一覧を取得する。
                resources = this.resourcePatternResolver.getResources("s3://" + BUCKET_NAME + "/**/*" + fileName + ".*");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            List<S3File> s3FileList = new ArrayList<S3File>();

            // リソース毎にS3Objectを取得する。
            for (Resource resource : resources) {
                S3Object s3Object = amazonS3.getObject(new GetObjectRequest("s3.cinpo", resource.getFilename()));

                // 画面表示項目の設定
                S3File s3File = new S3File();
                s3File.setBucketName(s3Object.getBucketName());
                s3File.setKey(s3Object.getKey());
                s3File.setContentType(s3Object.getObjectMetadata().getContentType());
                s3File.setLastModifiedDate(s3Object.getObjectMetadata().getLastModified());

                s3FileList.add(s3File);
            }
            s3FileForm.setFileList(s3FileList);

        return s3FileForm;
    }
}

HTML

最後に、検索・一覧表示用の画面を作成する。

downloadFromS3
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">

    <head>
        <meta charset="UTF-8" />
        <link th:substituteby="common/header :: common_header"/>
        <title>S3からファイルダウンロード</title>
    </head>
    <body>
        <h1>File Download</h1>
        <p><a href="/index" th:href="@{/index}">Back to home</a></p>
        <form action="#" th:action="@{/aws/s3/search}" th:object="${s3FileForm}" method="post">
            <table>
                <tr>
                    <td width="100px"><label for="fileName">検索キー</label>:</td>
                    <td><input type="text" id="fileName" th:field="*{fileName}" size="30px" /></td>
                </tr>
                <tr>
                    <td colspan="2"><input type="submit" value="Search" /></td>
                </tr>
            </table>
        </form>
        <div th:if="${s3FileForm.fileList != null}">
            <form action="#" th:action="@{/aws/s3/download}" th:object="${s3FileForm}" method="post">
                <table border="1">
                    <tr>
                        <th width="20px">Download</th>
                        <th width="10px">Check</th>
                        <th>Bucket</th>
                        <th>Key</th>
                        <th>Content Type</th>
                        <th>Last Modified Date</th>
                    </tr>
                    <tr th:each="file:${s3FileForm.fileList}">
                        <td>
                            <button type="submit" name="downloadKey" th:field="*{downloadKey}" th:value="${file.key}">Download</button>
                        </td>
                        <td><input type="checkbox" th:field="*{checkedItems}" th:value="${file.key}" /></td>
                        <td th:text="${file.bucketName}"></td>
                        <td th:text="${file.key}"></td>
                        <td th:text="${file.contentType}"></td>
                        <td th:text="${file.lastModifiedDate}"></td>
                    </tr>
                </table>
                <div><input type="submit" value="Download Selected" /></div>
            </form>
        </div>
    </body>
</html>

参考サイド:Spring Cloud AWS