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

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

完成後画面のイメージ↓
![2017-07-26_174655.jpg](https://qiita-image-store.s3.amazonaws.com/0/123138/e88ffbc8-2e79-4956-1c3f-0eb33d8c6cc5.jpeg)


#検証環境
・Spring Boot（Thymeleaf）1.4.3

#必要なライブラリ

```xml: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」に指定する。

```properties: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コンフィグクラスを作成する。

```java: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クラスと画面表示用のフォームクラスを作成する。

```java: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";
	}
```

```java:S3FileForm 
public class S3FileForm implements Serializable {
	
	private String fileName;
	
	private String downloadKey;
	
	private String[] checkedItems;
	
	private List<S3File> fileList;

	//...get set省略
}
```

```java: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`を使えばアクセス可能な全バケットを対象に検索が可能です。

```java: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
最後に、検索・一覧表示用の画面を作成する。

```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](http://http://cloud.spring.io/spring-cloud-static/spring-cloud-aws/1.2.1.RELEASE/)