001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.cloud;
017
018import java.io.ByteArrayInputStream;
019import java.io.File;
020import java.io.FileFilter;
021import java.io.FileNotFoundException;
022import java.io.IOException;
023import java.io.InputStream;
024import java.util.ArrayList;
025import java.util.List;
026
027import org.opengion.fukurou.model.FileOperation;
028import org.opengion.fukurou.system.Closer;                                                      // 8.0.0.0 (2021/09/30) util.Closer → system.Closer
029import org.opengion.fukurou.util.StringUtil;
030import org.opengion.hayabusa.common.HybsSystem;
031import org.opengion.hayabusa.common.HybsSystemException;
032
033import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // HybsSystem.BUFFER_MIDDLE は fukurou に移動
034import static org.opengion.fukurou.system.HybsConst.CR ;                        // 8.0.0.1 (2021/10/08)
035
036import com.amazonaws.auth.InstanceProfileCredentialsProvider;
037import com.amazonaws.services.s3.AmazonS3;
038import com.amazonaws.services.s3.AmazonS3ClientBuilder;
039import com.amazonaws.services.s3.model.AmazonS3Exception;
040import com.amazonaws.services.s3.model.ListObjectsV2Request;
041import com.amazonaws.services.s3.model.ListObjectsV2Result;
042import com.amazonaws.services.s3.model.ObjectListing;
043import com.amazonaws.services.s3.model.ObjectMetadata;
044import com.amazonaws.services.s3.model.PutObjectRequest;
045import com.amazonaws.services.s3.model.S3Object;
046import com.amazonaws.services.s3.model.S3ObjectSummary;
047
048/**
049 * FileOperation_AWSは、S3ストレージに対して、
050 * ファイル操作を行うクラスです。
051 *
052 * 認証は下記の2通りが可能です。→ 1) の方法のみサポートします。
053 *  1)実行サーバのEC2のインスタンスに、S3ストレージのアクセス許可を付与する
054 * <del> 2)システムリソースにアクセスキー・シークレットキー・エンドポイント・レギオンを登録する
055 * (CLOUD_STORAGE_S3_ACCESS_KEY、CLOUD_STORAGE_S3_SECRET_KEY、CLOUD_STORAGE_S3_SERVICE_END_POINT、CLOUD_STORAGE_S3_REGION)
056 * </del>
057 *
058 * 注意:
059 * バケット名は全ユーザで共有のため、自身のバケット名か、作成されていないバケット名を指定する必要があります。
060 *
061 * @og.rev 5.10.8.0 (2019/02/01) 新規作成
062 *
063 * @version 5
064 * @author  oota
065 * @since   JDK7.0
066 */
067public class FileOperation_AWS extends CloudFileOperation {
068        private static final long serialVersionUID = 5108020190201L ;
069
070        private static final String PLUGIN = "AWS";                     // 8.0.0.1 (2021/10/08) staticと大文字化
071
072        /** クラス変数 */
073        private final AmazonS3 amazonS3;
074
075        /**
076         * コンストラクター
077         *
078         * 初期化処理です。
079         * AWSの認証処理を行います。
080         *
081         * @og.rev 8.0.0.1 (2021/10/08) CLOUD_STORAGE_S3… 関連の方法廃止
082         *
083         * @param bucket バケット
084         * @param inPath パス
085         */
086        public FileOperation_AWS(final String bucket, final String inPath) {
087                super(StringUtil.nval(bucket, HybsSystem.sys("CLOUD_BUCKET")), inPath);
088
089                // IAMロールによる認証
090                amazonS3 = AmazonS3ClientBuilder.standard()
091                                .withCredentials(new InstanceProfileCredentialsProvider(false))
092                                .build();
093
094                try {
095                        // S3に指定されたバケット(コンテナ)が存在しない場合は、作成する
096                        if (!amazonS3.doesBucketExistV2(conBucket)) { // doesBucketExistV2最新JARだと出ている
097                                amazonS3.createBucket(conBucket);
098                        }
099                } catch (final AmazonS3Exception ase) {
100                        final String errMsg = new StringBuilder(BUFFER_MIDDLE)
101                                                        .append("IAMロールによる認証が失敗しました。").append( CR )
102                                                        .append( inPath ).toString();
103
104                        throw new HybsSystemException(errMsg,ase);
105                }
106        }
107
108        /**
109         * 書き込み処理(評価用)
110         *
111         * Fileを書き込みます。
112         *
113         * @og.rev 8.0.0.1 (2021/10/08) 新規追加
114         * @og.rev 8.0.2.0 (2021/11/30) catch Exception → Throwable に変更。
115         *
116         * @param inFile 書き込みFile
117         * @throws IOException ファイル関連エラー情報
118         */
119        @Override
120        public void write(final File inFile) throws IOException {
121                try {
122                        amazonS3.putObject(conBucket, conPath, inFile);
123//              } catch (final Exception ex) {
124                } catch( final Throwable th ) {         // 8.0.2.0 (2021/11/30)
125                        final String errMsg = new StringBuilder(BUFFER_MIDDLE)
126                                                .append("AWSバケットに(File)書き込みが失敗しました。").append( CR )
127                                                .append(conPath).toString();
128                        throw new IOException(errMsg,th);
129                }
130        }
131
132        /**
133         * 書き込み処理
134         *
135         * InputStreamのデータを書き込みます。
136         *
137         * @og.rev 8.0.2.0 (2021/11/30) catch Exception → Throwable に変更。
138         *
139         * @param is 書き込みデータのInputStream
140         * @throws IOException ファイル関連エラー情報
141         */
142        @Override
143        public void write(final InputStream is) throws IOException {
144                ByteArrayInputStream bais = null;
145                try {
146                        final ObjectMetadata om = new ObjectMetadata();
147
148                        final byte[] bytes = toByteArray(is);
149                        om.setContentLength(bytes.length);
150                        bais = new ByteArrayInputStream(bytes);
151
152                        final PutObjectRequest request = new PutObjectRequest(conBucket, conPath, bais, om);
153
154                        amazonS3.putObject(request);
155//              } catch (final Exception ex) {
156                } catch( final Throwable th ) {         // 8.0.2.0 (2021/11/30)
157                        final String errMsg = new StringBuilder(BUFFER_MIDDLE)
158                                                .append("AWSバケットに(InputStream)書き込みが失敗しました。").append( CR )
159                                                .append(conPath).toString();
160                        throw new IOException(errMsg,th);
161                } finally {
162                        Closer.ioClose(bais);
163                }
164        }
165
166        /**
167         * 読み込み処理
168         *
169         * データを読み込み、InputStreamとして、返します。
170         *
171         * @og.rev 8.0.2.0 (2021/11/30) catch Exception → Throwable に変更。
172         *
173         * @return 読み込みデータのInputStream
174         * @throws FileNotFoundException ファイル非存在エラー情報
175         */
176        @Override
177        public InputStream read() throws FileNotFoundException {
178                S3Object object = null;
179
180                try {
181                        object = amazonS3.getObject(conBucket, conPath);
182//              } catch (final Exception ex) {
183                } catch( final Throwable th ) {         // 8.0.2.0 (2021/11/30)
184                        final String errMsg = new StringBuilder(BUFFER_MIDDLE)
185                                                .append("AWSバケットから読み込みが失敗しました。").append( CR )
186                                                .append(conPath).append( CR )
187                                                .append( th.getMessage() ).toString();
188                        throw new FileNotFoundException(errMsg);                                // FileNotFoundException は、Throwable 引数を持つコンストラクタはなない。
189                }
190                return object.getObjectContent();       // com.amazonaws.services.s3.model.S3ObjectInputStream
191        }
192
193        /**
194         * 削除処理
195         *
196         * ファイルを削除します。
197         *
198         * @og.rev 8.0.2.0 (2021/11/30) catch Exception → Throwable に変更。
199         *
200         * @return 成否フラグ
201         */
202        @Override
203        public boolean delete() {
204                boolean flgRtn = false;
205
206                try {
207                        if (isFile()) {
208                                // ファイル削除
209                                amazonS3.deleteObject(conBucket, conPath);
210                        } else if (isDirectory()) {
211                                // ディレクトリ削除
212                                // 一括削除のapiが無いので、繰り返しで削除を行う
213                                final ObjectListing objectList = amazonS3.listObjects(conBucket, conPath);
214                                final List<S3ObjectSummary> list = objectList.getObjectSummaries();
215                                for (final S3ObjectSummary obj : list) {
216                                        amazonS3.deleteObject(conBucket, obj.getKey());
217                                }
218                        }
219                        flgRtn = true;
220//              } catch (final Exception ex) {
221                } catch( final Throwable th ) {         // 8.0.2.0 (2021/11/30)
222                        // エラーはスルーして、falseを返す
223                        System.out.println( th.getMessage() );
224                }
225
226                return flgRtn;
227        }
228
229        /**
230         * コピー処理
231         *
232         * ファイルを指定先に、コピーします。
233         *
234         * @og.rev 8.0.2.0 (2021/11/30) catch Exception → Throwable に変更。
235         *
236         * @param afPath コピー先
237         * @return 成否フラグ
238         */
239        @Override
240        public boolean copy(final String afPath) {
241                boolean flgRtn = false;
242
243                try {
244                        amazonS3.copyObject(conBucket, conPath, conBucket, afPath);
245                        flgRtn = true;
246//              } catch (final Exception ex) {
247                } catch( final Throwable th ) {         // 8.0.2.0 (2021/11/30)
248                        // エラーはスルーして、falseを返す
249                        System.out.println( th.getMessage() );
250                }
251
252                return flgRtn;
253        }
254
255        /**
256         * ファイルサイズ取得
257         *
258         * ファイルサイズを返します。
259         *
260         * @og.rev 8.0.2.0 (2021/11/30) catch Exception → Throwable に変更。
261         *
262         * @return ファイルサイズ
263         */
264        @Override
265        public long length() {
266                long rtn = 0;
267
268                try {
269                        final ObjectMetadata meta = amazonS3.getObjectMetadata(conBucket, conPath);
270                        rtn = meta.getContentLength();
271//              } catch (final Exception ex) {
272                } catch( final Throwable th ) {         // 8.0.2.0 (2021/11/30)
273                        // エラーはスルーして、0を返す。
274                        System.out.println( th.getMessage() );
275                }
276                return rtn;
277        }
278
279        /**
280         * 最終更新時刻取得
281         *
282         * 最終更新時刻を取得します。
283         *
284         * @og.rev 8.0.2.0 (2021/11/30) catch Exception → Throwable に変更。
285         *
286         * @return 最終更新時刻
287         */
288        @Override
289        public long lastModified() {
290                long rtn = 0;
291
292                try {
293                        final ObjectMetadata meta = amazonS3.getObjectMetadata(conBucket, conPath);
294                        rtn = meta.getLastModified().getTime();
295//              } catch (final Exception ex) {
296                } catch( final Throwable th ) {         // 8.0.2.0 (2021/11/30)
297                        // エラーはスルーして、0を返す
298                        System.out.println( th.getMessage() );
299                }
300                return rtn;
301        }
302
303        /**
304         * ファイル判定
305         *
306         * ファイルの場合は、trueを返します。
307         *
308         * @return ファイル判定フラグ
309         */
310        @Override
311        public boolean isFile() {
312                boolean rtn = false;
313
314                if(!isDirectory()) {
315                        rtn = amazonS3.doesObjectExist(conBucket, conPath);
316                }
317
318                return rtn;
319        }
320
321        /**
322         * ディレクトリ判定
323         *
324         * ディレクトリの場合は、trueを返します。
325         *
326         * @return ディレクトリ判定フラグ
327         */
328        @Override
329        public boolean isDirectory() {
330                if (StringUtil.isEmpty(conPath)) {              // 8.0.1.0 (2021/10/29) org.apache.commons.lang3.StringUtils 置換
331                        return true;
332                }
333
334                // S3にはディレクトリの概念はないので、「/」で続くデータが存在するかで、判定
335                final ObjectListing objectList = amazonS3.listObjects(conBucket, setDirTail(conPath));
336                final List<S3ObjectSummary> list = objectList.getObjectSummaries();
337
338//              return list.size() != 0 ;
339                return ! list.isEmpty() ;
340        }
341
342        /**
343         * ファイル一覧取得
344         *
345         * パスのファイルとディレクトリ一覧を取得します。
346         *
347         * @og.rev 8.0.2.0 (2021/11/30) fukurou.util.rTrim(String,char) 使用
348         *
349         * @param filter フィルタ情報
350         * @return ファイルとティレクトリ一覧
351         */
352        @Override
353        public File[] listFiles(final FileFilter filter) {
354                if (!exists()) {
355                        return new FileOperationInfo[0];
356                }
357
358                String search = conPath;
359                if (isDirectory()) {
360                        search = setDirTail(conPath);
361                }
362
363                final List<File> rtnList = new ArrayList<File>();
364
365                // 検索処理
366                final ListObjectsV2Request request = new ListObjectsV2Request()
367                                .withBucketName(conBucket)
368                                .withPrefix(search)
369                                .withDelimiter("/");
370                final ListObjectsV2Result list = amazonS3.listObjectsV2(request);
371                final List<S3ObjectSummary> objects = list.getObjectSummaries();
372
373                // ファイル情報の取得
374                for (final S3ObjectSummary obj : objects) {
375                        final String key = obj.getKey();
376
377                        final FileOperationInfo file = new FileOperationInfo(PLUGIN, conBucket, key);
378                        file.setLastModifiedValue(obj.getLastModified().getTime());
379                        file.setFile(true);
380                        file.setSize(obj.getSize());
381                        rtnList.add(file);
382                }
383
384                // ディレクトリ情報の取得
385                final List<String> folders = list.getCommonPrefixes();
386                for (final String str : folders) {
387//                      final String key = rTrim(str, '/');
388                        final String key = StringUtil.rTrim(str, '/');                  // 8.0.2.0 (2021/11/30)
389
390                        final FileOperationInfo file = new FileOperationInfo(PLUGIN, conBucket, key);
391                        file.setDirectory(true);
392                        rtnList.add(file);
393                }
394
395                // フィルタ処理
396                return filter(rtnList, filter);
397        }
398
399        /**
400         * 親ディレクトリ情報の取得
401         *
402         * 親のディレクトリを返します。
403         *
404         * @return 親のディレクトリ情報
405         */
406        @Override
407        public FileOperation getParentFile() {
408                return new FileOperation_AWS(conBucket, this.getParent());
409        }
410}