001package org.opengion.plugin.cloud;
002
003import java.io.ByteArrayInputStream;
004import java.io.FileNotFoundException;
005import java.io.IOException;
006import java.io.InputStream;
007import java.util.ArrayList;
008import java.util.List;
009
010import org.apache.commons.lang3.StringUtils;
011import org.opengion.fukurou.model.AbstractFileOperation;
012import org.opengion.fukurou.model.FileOperation;
013import org.opengion.fukurou.model.FileOperationFileFilter;
014import org.opengion.fukurou.model.FileOperationInfo;
015import org.opengion.fukurou.util.Closer;
016import org.opengion.fukurou.util.StringUtil;
017import org.opengion.hayabusa.common.HybsSystem;
018import org.opengion.hayabusa.common.HybsSystemException;
019
020import com.amazonaws.auth.AWSCredentials;
021import com.amazonaws.auth.AWSStaticCredentialsProvider;
022import com.amazonaws.auth.BasicAWSCredentials;
023import com.amazonaws.auth.InstanceProfileCredentialsProvider;
024import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration;
025import com.amazonaws.services.s3.AmazonS3;
026import com.amazonaws.services.s3.AmazonS3ClientBuilder;
027import com.amazonaws.services.s3.model.AmazonS3Exception;
028import com.amazonaws.services.s3.model.ListObjectsV2Request;
029import com.amazonaws.services.s3.model.ListObjectsV2Result;
030import com.amazonaws.services.s3.model.ObjectListing;
031import com.amazonaws.services.s3.model.ObjectMetadata;
032import com.amazonaws.services.s3.model.PutObjectRequest;
033import com.amazonaws.services.s3.model.S3Object;
034import com.amazonaws.services.s3.model.S3ObjectSummary;
035
036/**
037 * FileOperation_AWSは、S3ストレージに対して、
038 * ファイル操作を行うクラスです。
039 * 
040 * 認証は下記の2通りが可能です。
041 * ・実行サーバのEC2のインスタンスに、S3ストレージのアクセス許可を付与する
042 * ・システムリソースにアクセスキー・シークレットキー・エンドポイント・レギオンを登録する
043 * (CLOUD_STORAGE_S3_ACCESS_KEY、CLOUD_STORAGE_S3_SECRET_KEY、CLOUD_STORAGE_S3_SERVICE_END_POINT、CLOUD_STORAGE_S3_REGION)
044 * 
045 * 注意:
046 * バケット名は全ユーザで共有のため、自身のバケット名か、作成されていないバケット名を指定する必要があります。
047 * 
048 * @og.rev 5.10.8.0 (2019/02/01) 新規作成
049 *
050 * @version 5
051 * @author  oota
052 * @sinse   JDK7.0
053 */
054public class FileOperation_AWS extends AbstractFileOperation {
055        /** クラス変数 */
056        private final AmazonS3 amazonS3;
057        private final String conBuket;
058
059        /**
060         * コンストラクター
061         * @param buket バケット
062         * @param inPath パス
063         */
064        public FileOperation_AWS(String buket, String  inPath) {
065                super( StringUtil.nval( buket, HybsSystem.sys("CLOUD_BUCKET") ), inPath);
066                conBuket = buket;
067
068                // アクセスキー
069                final String s3AccessKey = HybsSystem.sys("CLOUD_STORAGE_S3_ACCESS_KEY");
070                String s3SecretKey = "";
071                String s3ServiceEndPoint = "";
072                String s3Region = "";
073
074                // S3アクセスクライアントの生成
075                if (StringUtils.isEmpty(s3AccessKey)) {
076                        // IAMロールによる認証
077                        amazonS3 = AmazonS3ClientBuilder.standard()
078                                        .withCredentials(new InstanceProfileCredentialsProvider(false))
079                                        .build();
080                } else {
081                        // リソースのアクセスキーによる認証
082                        // シークレットキー
083                        s3SecretKey = HybsSystem.sys("CLOUD_STORAGE_S3_SECRET_KEY");
084                        // エンドポイント
085                        s3ServiceEndPoint = HybsSystem.sys("CLOUD_STORAGE_S3_SERVICE_END_POINT");
086                        // レギオン
087                        s3Region = HybsSystem.sys("CLOUD_STORAGE_S3_REGION");
088
089                        // AWSの認証情報
090                        AWSCredentials credentials = new BasicAWSCredentials(s3AccessKey, s3SecretKey);
091
092                        // エンドポイント設定
093                        EndpointConfiguration endpointConfiguration = new EndpointConfiguration(s3ServiceEndPoint, s3Region);
094                        amazonS3 = AmazonS3ClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(credentials))
095                                        .withEndpointConfiguration(endpointConfiguration)
096                                        .build();
097                }
098
099                try {
100                        // S3に指定されたバケット(コンテナ)が存在しない場合は、作成する
101                        if (!amazonS3.doesBucketExist(bucket)) { // doesBucketExistV2最新JARだと出ている
102                                amazonS3.createBucket(bucket);
103                        }
104                } catch (AmazonS3Exception ase) {
105                        StringBuilder errMsg = new StringBuilder(HybsSystem.BUFFER_MIDDLE);
106                        if (StringUtils.isEmpty(s3AccessKey)) {
107                                errMsg.append("IAMロールによる認証が失敗しました。");
108                                
109                        } else {
110                                errMsg.append("アクセスキーによる認証が失敗しました。");
111                                errMsg.append(" CLOUD_STORAGE_S3_ACCESS_KEY:").append(s3AccessKey);
112                                errMsg.append(" CLOUD_STORAGE_S3_SECRET_KEY:非表示");
113                                errMsg.append(" CLOUD_STORAGE_S3_SERVICE_END_POINT:").append(s3ServiceEndPoint);
114                                errMsg.append(" CLOUD_STORAGE_S3_REGION:").append(s3Region);
115                        }
116                        errMsg.append(" システムエラー情報:").append(ase.getMessage());
117                        throw new HybsSystemException(errMsg.toString());
118                }
119        }
120
121        /**
122         * InputStreamのデータを書き込みます。
123         * 
124         * @param is 書き込みデータのInputStream
125         * @throws IOException
126         */
127        @Override
128        public void write(InputStream is) throws IOException {
129                ByteArrayInputStream bais = null;
130                try {
131                        ObjectMetadata om = new ObjectMetadata();
132
133                        byte[] bytes = toByteArray(is);
134                        om.setContentLength(bytes.length);
135                        bais = new ByteArrayInputStream(bytes);
136
137                        PutObjectRequest request = new PutObjectRequest(bucket, path, bais, om);
138
139                        amazonS3.putObject(request);
140                } catch (Exception e) {
141                        StringBuilder errMsg = new StringBuilder(HybsSystem.BUFFER_MIDDLE);
142                        errMsg.append("AWSバケットに書き込みが失敗しました。path:").append(path);
143                        errMsg.append(" システムエラー情報:").append(e.getMessage());
144                        throw new IOException(errMsg.toString());
145                } finally {
146                        Closer.ioClose(bais);
147                }
148        }
149
150        /**
151         * データを読み込み、InputStreamとして、返します。
152         * 
153         * @return 読み込みデータのInputStream
154         * @throws FileNotFoundException
155         */
156        @Override
157        public InputStream read() throws FileNotFoundException {
158                S3Object object = null;
159
160                try {
161                        object = amazonS3.getObject(bucket, path);
162                } catch (Exception e) {
163                        StringBuilder errMsg = new StringBuilder(HybsSystem.BUFFER_MIDDLE);
164                        errMsg.append("AWSバケットから読み込みが失敗しました。path:").append(path);
165                        errMsg.append(" システムエラー情報:").append(e.getMessage());
166                        throw new FileNotFoundException(errMsg.toString());
167                }
168                return object.getObjectContent();
169        }
170
171        /**
172         * ファイルを削除します。
173         * 
174         * @return 成否フラグ
175         */
176        @Override
177        public boolean delete() {
178                boolean flgRtn = false;
179
180                try {
181                        if (isFile()) {
182                                // ファイル削除
183                                amazonS3.deleteObject(bucket, path);
184                        } else if (isDirectory()) {
185                                // ディレクトリ削除
186                                // 一括削除のapiが無いので、繰り返しで削除を行う
187                                ObjectListing objectList = amazonS3.listObjects(bucket, path);
188                                List<S3ObjectSummary> list = objectList.getObjectSummaries();
189                                for (S3ObjectSummary obj : list) {
190                                        amazonS3.deleteObject(bucket, obj.getKey());
191                                }
192
193                        }
194                        flgRtn = true;
195                } catch (Exception e) {
196                        // エラーはスルーして、falseを返す
197                }
198
199                return flgRtn;
200        }
201
202        /**
203         * ファイルを指定先に、コピーします。
204         * 
205         * @param afPath コピー先
206         * @return 成否フラグ
207         */
208        @Override
209        public boolean copy(String afPath) {
210                boolean flgRtn = false;
211
212                try {
213                        amazonS3.copyObject(bucket, path, bucket, afPath);
214                        flgRtn = true;
215                } catch (Exception e) {
216                        // エラーはスルーして、falseを返す
217                }
218
219                return flgRtn;
220        }
221
222        /**
223         * ファイルサイズを返します
224         * 
225         * @return ファイルサイズ
226         */
227        @Override
228        public long length() {
229                long rtn = 0;
230
231                try {
232                        ObjectMetadata meta = amazonS3.getObjectMetadata(bucket, path);
233                        rtn = meta.getContentLength();
234                } catch (Exception e) {
235                        // エラーはスルーして、0を返す。
236                }
237                return rtn;
238        }
239
240        /**
241         * 最終更新時刻を取得します。
242         * 
243         * @return 最終更新時刻
244         */
245        @Override
246        public long lastModified() {
247                long rtn = 0;
248
249                try {
250                        ObjectMetadata meta = amazonS3.getObjectMetadata(bucket, path);
251                        rtn = meta.getLastModified().getTime();
252                } catch (Exception e) {
253                        // エラーはスルーして、0を返す
254                }
255                return rtn;
256        }
257
258        /**
259         * ファイルの場合は、trueを返します。
260         * 
261         * @return ファイルフラグ
262         */
263        @Override
264        public boolean isFile() {
265                return amazonS3.doesObjectExist(bucket, path);
266        }
267
268        /**
269         * ディレクトリの場合は、trueを返します。
270         * 
271         * @return ディレクトリフラグ
272         */
273        @Override
274        public boolean isDirectory() {
275                boolean flgRtn = false;
276
277                if (StringUtils.isEmpty(path)) {
278                        return true;
279                }
280
281                // S3にはディレクトリの概念はないので、「/」で続くデータが存在するかで、判定
282                ObjectListing objectList = amazonS3.listObjects(bucket, setDirTail(path));
283                List<S3ObjectSummary> list = objectList.getObjectSummaries();
284                flgRtn = list.size() == 0 ? false : true;
285
286                return flgRtn;
287        }
288
289        /**
290         * パスのファイルとディレクトリ一覧を取得します。
291         * 
292         * @return ファイルとティレクトリ一覧
293         */
294        @Override
295        public FileOperation[] listFiles(FileOperationFileFilter filter) {
296                if (!exists()) {
297                        return new FileOperationInfo[0];
298                }
299
300                String search = path;
301                if (isDirectory()) {
302                        search = setDirTail(path);
303                }
304
305                List<FileOperationInfo> rtnList = new ArrayList<FileOperationInfo>();
306
307                // 検索処理
308                ListObjectsV2Request request = new ListObjectsV2Request()
309                                .withBucketName(bucket)
310                                .withPrefix(search)
311                                .withDelimiter("/");
312                ListObjectsV2Result list = amazonS3.listObjectsV2(request);
313                List<S3ObjectSummary> objects = list.getObjectSummaries();
314
315                // ファイル情報の取得
316                for (S3ObjectSummary obj : objects) {
317                        String key = obj.getKey();
318
319                        FileOperationInfo file = new FileOperationInfo();
320                        file.setPath(key);
321                        file.setName(drawName(key));
322                        file.setParent(drawParent(key));
323                        file.setLastModifiedValue(obj.getLastModified().getTime());
324                        file.setFile(true);
325                        file.setSize(obj.getSize());
326                        rtnList.add(file);
327                }
328
329                // ディレクトリ情報の取得
330                List<String> folders = list.getCommonPrefixes();
331                for (String str : folders) {
332                        String key = rTrim(str, '/');
333
334                        FileOperationInfo file = new FileOperationInfo();
335                        file.setPath(key);
336                        file.setName(drawName(key));
337                        file.setParent(drawParent(key));
338                        file.setDirectory(true);
339                        rtnList.add(file);
340                }
341
342                // フィルタ処理
343                FileOperation[] filterList = filter(rtnList, filter);
344
345                return filterList;
346        }
347
348        /**
349         * 親のディレクトリを返します。
350         * 
351         * @return 親のディレクトリ
352         */
353        @Override
354        public FileOperation getParentFile() {
355                return new FileOperation_AWS(conBuket,getParent());
356        }
357
358                        /** 以下はローカル環境でのテスト用メソッドです。 */
359//                      /**
360//                       * ローカルでのテスト用コンストラクタ
361//                       * 
362//                       * 要:super.bucketの値は固定値に変更してください。
363//                       * 
364//                       * @param inPath
365//                       */
366//                      public FileOperation_AWS(String buket, String inPath, boolean test) {
367//                              // super( StringUtil.nval( buket, HybsSystem.sys("CLOUD_BUCKET") ), inPath);
368//                              super( StringUtil.nval( buket, "opengiontestbucket" ), inPath);
369//                              conBuket = buket;
370//              
371//                              // aws接続情報
372//                               String s3AccessKey = "[キー]";
373//                               String s3SecretKey = "[シークレットキー]";
374//                              String s3ServiceEndPoint = "s3-ap-northeast-1.amazonaws.com";
375//                              String s3Region = "ap-northeast-1";
376//                              
377//                              // proxy環境での設定
378//                              ClientConfiguration conf = new ClientConfiguration();
379//                              conf.setProtocol(Protocol.HTTPS);
380//                              conf.setProxyHost("mtc-px14");
381//                              conf.setProxyPort(8081);
382//              
383//                              // AWSの認証情報
384//                              AWSCredentials credentials = new BasicAWSCredentials(s3AccessKey, s3SecretKey);
385//              
386//                              // エンドポイント設定
387//                              EndpointConfiguration endpointConfiguration = new EndpointConfiguration(s3ServiceEndPoint, s3Region);
388//                              amazonS3 = AmazonS3ClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(credentials))
389//                                              .withEndpointConfiguration(endpointConfiguration)
390//                                              .withClientConfiguration(conf) // テスト用にproxy設定
391//                                              .build();
392//              
393//                              try {
394//                                      // S3に指定されたバケット(コンテナ)が存在しない場合は、作成する
395//                                      if (!amazonS3.doesBucketExistV2(bucket)) {
396//                                              amazonS3.createBucket(bucket);
397//                                      }
398//                              } catch (AmazonS3Exception ase) {
399//                                      StringBuilder errMsg = new StringBuilder(HybsSystem.BUFFER_MIDDLE);
400//                                      if (StringUtils.isEmpty(s3AccessKey)) {
401//                                              errMsg.append("IAMロールによる認証か、バケットの作成が失敗しました。");
402//                                              
403//                                      } else {
404//                                              errMsg.append("アクセスキーによる認証かバケットの作成が失敗しました。");
405//                                              errMsg.append(" CLOUD_STORAGE_S3_ACCESS_KEY:").append(s3AccessKey);
406//                                              errMsg.append(" CLOUD_STORAGE_S3_SECRET_KEY:非表示");
407//                                              errMsg.append(" CLOUD_STORAGE_S3_SERVICE_END_POINT:").append(s3ServiceEndPoint);
408//                                              errMsg.append(" CLOUD_STORAGE_S3_REGION:").append(s3Region);
409//                                      }
410//                                      errMsg.append(" バケット名:").append(bucket);
411//                                      errMsg.append(" システムエラー情報:").append(ase.getMessage());
412//                                      throw new RuntimeException(errMsg.toString());
413//                              }
414//                      }
415//              
416//                      /** テスト用メソッド */
417//                      public static void main(String[] args) {
418//                              writeTest();
419////                            listTest();
420////                            methodTest();
421//                      }
422//                      
423//                      public static void writeTest() {
424//                              FileOperation file = new FileOperation_AWS("", "sample/test.txt", true);
425//                              
426//                              try(InputStream is = new ByteArrayInputStream("sample".getBytes())){
427//                                      file.write(is);
428//                              }catch(Exception e) {
429//                                      System.out.println(e.getMessage());
430//                              }
431//                      }
432//                      
433//                      public static void listTest() {
434//                              FileOperation file = new FileOperation_AWS("", "sample", true);
435//                              
436//                              // フィルタ設定
437//                              HybsFileFilter filter = new HybsFileFilter();
438//                              filter.startsWith("te");
439//                              
440//                              FileOperation[] list = file.listFiles(filter);
441//                              System.out.println(list.length);
442//                              for(FileOperation f: list) {
443//                                      System.out.println(f.getPath());
444//                              }
445//                      }
446//                      
447//                      public static void methodTest() {
448//                              FileOperation file = new FileOperation_AWS("", "test", true);
449//                              boolean rtn = false;
450//                              rtn = file.isFile();
451//                              
452//                              System.out.println(rtn);
453//                      }
454}