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         *
115         * @param inFile 書き込みFile
116         * @throws IOException ファイル関連エラー情報
117         */
118        @Override
119        public void write(final File inFile) throws IOException {
120                try {
121                        amazonS3.putObject(conBucket, conPath, inFile);
122                } catch (final Exception ex) {
123                        final String errMsg = new StringBuilder(BUFFER_MIDDLE)
124                                                .append("AWSバケットに(File)書き込みが失敗しました。").append( CR )
125                                                .append(conPath).toString();
126                        throw new IOException(errMsg,ex);
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                        final ObjectMetadata om = new ObjectMetadata();
143
144                        final byte[] bytes = toByteArray(is);
145                        om.setContentLength(bytes.length);
146                        bais = new ByteArrayInputStream(bytes);
147
148                        final PutObjectRequest request = new PutObjectRequest(conBucket, conPath, bais, om);
149
150                        amazonS3.putObject(request);
151                } catch (final Exception ex) {
152                        final String errMsg = new StringBuilder(BUFFER_MIDDLE)
153                                                .append("AWSバケットに(InputStream)書き込みが失敗しました。").append( CR )
154                                                .append(conPath).toString();
155                        throw new IOException(errMsg,ex);
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 (final Exception ex) {
176                        final String errMsg = new StringBuilder(BUFFER_MIDDLE)
177                                                .append("AWSバケットから読み込みが失敗しました。").append( CR )
178                                                .append(conPath).append( CR )
179                                                .append( ex.getMessage() ).toString();
180                        throw new FileNotFoundException(errMsg);
181                }
182                return object.getObjectContent();       // com.amazonaws.services.s3.model.S3ObjectInputStream
183        }
184
185        /**
186         * 削除処理
187         *
188         * ファイルを削除します。
189         *
190         * @return 成否フラグ
191         */
192        @Override
193        public boolean delete() {
194                boolean flgRtn = false;
195
196                try {
197                        if (isFile()) {
198                                // ファイル削除
199                                amazonS3.deleteObject(conBucket, conPath);
200                        } else if (isDirectory()) {
201                                // ディレクトリ削除
202                                // 一括削除のapiが無いので、繰り返しで削除を行う
203                                final ObjectListing objectList = amazonS3.listObjects(conBucket, conPath);
204                                final List<S3ObjectSummary> list = objectList.getObjectSummaries();
205                                for (final S3ObjectSummary obj : list) {
206                                        amazonS3.deleteObject(conBucket, obj.getKey());
207                                }
208                        }
209                        flgRtn = true;
210                } catch (final Exception ex) {
211                        // エラーはスルーして、falseを返す
212                        System.out.println( ex.getMessage() );
213                }
214
215                return flgRtn;
216        }
217
218        /**
219         * コピー処理
220         *
221         * ファイルを指定先に、コピーします。
222         *
223         * @param afPath コピー先
224         * @return 成否フラグ
225         */
226        @Override
227        public boolean copy(final String afPath) {
228                boolean flgRtn = false;
229
230                try {
231                        amazonS3.copyObject(conBucket, conPath, conBucket, afPath);
232                        flgRtn = true;
233                } catch (final Exception ex) {
234                        // エラーはスルーして、falseを返す
235                        System.out.println( ex.getMessage() );
236                }
237
238                return flgRtn;
239        }
240
241        /**
242         * ファイルサイズ取得
243         *
244         * ファイルサイズを返します。
245         *
246         * @return ファイルサイズ
247         */
248        @Override
249        public long length() {
250                long rtn = 0;
251
252                try {
253                        final ObjectMetadata meta = amazonS3.getObjectMetadata(conBucket, conPath);
254                        rtn = meta.getContentLength();
255                } catch (final Exception ex) {
256                        // エラーはスルーして、0を返す。
257                        System.out.println( ex.getMessage() );
258                }
259                return rtn;
260        }
261
262        /**
263         * 最終更新時刻取得
264         *
265         * 最終更新時刻を取得します。
266         *
267         * @return 最終更新時刻
268         */
269        @Override
270        public long lastModified() {
271                long rtn = 0;
272
273                try {
274                        final ObjectMetadata meta = amazonS3.getObjectMetadata(conBucket, conPath);
275                        rtn = meta.getLastModified().getTime();
276                } catch (final Exception ex) {
277                        // エラーはスルーして、0を返す
278                        System.out.println( ex.getMessage() );
279                }
280                return rtn;
281        }
282
283        /**
284         * ファイル判定
285         *
286         * ファイルの場合は、trueを返します。
287         *
288         * @return ファイル判定フラグ
289         */
290        @Override
291        public boolean isFile() {
292                boolean rtn = false;
293
294                if(!isDirectory()) {
295                        rtn = amazonS3.doesObjectExist(conBucket, conPath);
296                }
297
298                return rtn;
299        }
300
301        /**
302         * ディレクトリ判定
303         *
304         * ディレクトリの場合は、trueを返します。
305         *
306         * @return ディレクトリ判定フラグ
307         */
308        @Override
309        public boolean isDirectory() {
310                if (StringUtil.isEmpty(conPath)) {              // 8.0.1.0 (2021/10/29) org.apache.commons.lang3.StringUtils 置換
311                        return true;
312                }
313
314                // S3にはディレクトリの概念はないので、「/」で続くデータが存在するかで、判定
315                final ObjectListing objectList = amazonS3.listObjects(conBucket, setDirTail(conPath));
316                final List<S3ObjectSummary> list = objectList.getObjectSummaries();
317
318                return list.size() != 0 ;
319        }
320
321        /**
322         * ファイル一覧取得
323         *
324         * パスのファイルとディレクトリ一覧を取得します。
325         *
326         * @param filter フィルタ情報
327         * @return ファイルとティレクトリ一覧
328         */
329        @Override
330        public File[] listFiles(final FileFilter filter) {
331                if (!exists()) {
332                        return new FileOperationInfo[0];
333                }
334
335                String search = conPath;
336                if (isDirectory()) {
337                        search = setDirTail(conPath);
338                }
339
340                final List<File> rtnList = new ArrayList<File>();
341
342                // 検索処理
343                final ListObjectsV2Request request = new ListObjectsV2Request()
344                                .withBucketName(conBucket)
345                                .withPrefix(search)
346                                .withDelimiter("/");
347                final ListObjectsV2Result list = amazonS3.listObjectsV2(request);
348                final List<S3ObjectSummary> objects = list.getObjectSummaries();
349
350                // ファイル情報の取得
351                for (final S3ObjectSummary obj : objects) {
352                        final String key = obj.getKey();
353
354                        final FileOperationInfo file = new FileOperationInfo(PLUGIN, conBucket, key);
355                        file.setLastModifiedValue(obj.getLastModified().getTime());
356                        file.setFile(true);
357                        file.setSize(obj.getSize());
358                        rtnList.add(file);
359                }
360
361                // ディレクトリ情報の取得
362                final List<String> folders = list.getCommonPrefixes();
363                for (final String str : folders) {
364                        final String key = rTrim(str, '/');
365
366                        final FileOperationInfo file = new FileOperationInfo(PLUGIN, conBucket, key);
367                        file.setDirectory(true);
368                        rtnList.add(file);
369                }
370
371                // フィルタ処理
372                return filter(rtnList, filter);
373        }
374
375        /**
376         * 親ディレクトリ情報の取得
377         *
378         * 親のディレクトリを返します。
379         *
380         * @return 親のディレクトリ情報
381         */
382        @Override
383        public FileOperation getParentFile() {
384                return new FileOperation_AWS(conBucket, this.getParent());
385        }
386}