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