    001package org.opengion.fukurou.model;
    002
    003import java.io.ByteArrayInputStream;
    004import java.io.ByteArrayOutputStream;
    005import java.io.File;
    006import java.io.FileFilter;
    007import java.io.FileNotFoundException;
    008import java.io.IOException;
    009import java.io.InputStream;
    010import java.net.URI;
    011import java.util.ArrayList;
    012import java.util.List;
    013import java.util.regex.Matcher;
    014import java.util.regex.Pattern;
    015
    016import org.opengion.fukurou.util.Closer;
    017import org.opengion.fukurou.util.StringUtil;
    018
    019/**
    020 * クラウドストレージ対応用の抽象クラスです。
    021 * 各ベンダーのストレージに対応したプラグインを作成する場合はこのクラスを継承してください。
    022 *
    023 *
    024 * @og.group ファイル操作
    025 *
    026 * @og.rev 5.10.8.0 (2019/02/01) 新規作成
    027 * @og.rev 5.10.9.0 (2019/03/01) 変更対応
    028 * @author oota
    029 * @since JDK7.0
    030 */
    031public abstract class CloudFileOperation extends FileOperation {
    032
    033        /* クラス定数 */
    034        private static final int BUFFER_SIZE = 1024 * 4;
    035        /* クラス変数 */
    036        // パス
    037        protected final String conPath;
    038        // バケット名
    039        protected final String conBucket;
    040
    041        private static final String UNIMPLEMNTED_ERR="このクラスでは未実装のメソッドです。";
    042        private static final char   FS = '/' ;
    043        // 5.10.12.2 (2019/06/17) 相対パス対応「../」と１つ前のディレクトリ情報を抽出(１つ前が先頭の場合は、/ではなく^)
    044        private static final Pattern ptnPreDir = Pattern.compile("(?<=/|^)[^/]+/\\.\\./");
    045
    046        /**
    047         * コンストラクタ
    048         *
    049         *
    050         * @param bucket バケット名
    051         * @param inPath ファイルパス
    052         */
    053        public CloudFileOperation(final String bucket, final String inPath) {
    054                super(inPath);
    055
    056                this.conPath = editPath(replaceFileSeparetor(inPath));
    057
    058                this.conBucket = bucket;
    059
    060                if (StringUtil.isNull(conBucket)) {
    061                        final String errMsg = "バケット未指定です。hayabusa利用ではシステム変数の「CLOUD_BUCKET」にバケット名を設定して下さい。";
    062                        throw new RuntimeException(errMsg);
    063                }
    064        }
    065
    066        /**
    067         * データ書き込み
    068         *
    069         * InputStreamのデータを書き込みます。
    070         *
    071         * @param is 書き込みデータのInputStream
    072         * @throws IOException IO関連のエラー情報
    073         */
    074        @Override
    075        public abstract void write(InputStream is) throws IOException;
    076
    077        /**
    078         * データ読み込み
    079         *
    080         * データを読み込み、InputStreamを返します。
    081         *
    082         * @return 読み込みデータのInputStream
    083         * @throws FileNotFoundException ファイル非存在エラー情報
    084         */
    085        @Override
    086        public abstract InputStream read() throws FileNotFoundException;
    087
    088        /**
    089         * ファイル削除
    090         *
    091         * ファイルを削除します。
    092         *
    093         * @return 成否フラグ
    094         */
    095        @Override
    096        public abstract boolean delete();
    097
    098        /**
    099         * ファイルコピー
    100         *
    101         * ファイルを指定先にコピーします。
    102         *
    103         * @param afPath コピー先
    104         * @return 成否フラグ
    105         */
    106        @Override
    107        public abstract boolean copy(String afPath);
    108
    109        /**
    110         * ファイルサイズ取得
    111         *
    112         * ファイルサイズを返します。
    113         *
    114         * @return ファイルサイズ
    115         */
    116        @Override
    117        public abstract long length();
    118
    119        /**
    120         * 最終更新時刻取得
    121         *
    122         * 最終更新時刻を返します。
    123         *
    124         * @return 最終更新時刻
    125         */
    126        @Override
    127        public abstract long lastModified();
    128
    129        /**
    130         * ファイル判定
    131         *
    132         * ファイルの場合は、trueを返します。
    133         *
    134         * @return ファイルフラグ
    135         */
    136        @Override
    137        public abstract boolean isFile();
    138
    139        /**
    140         * ディレクトリ判定
    141         *
    142         * ディレクトリの場合は、trueを返します。
    143         *
    144         * @return ディレクトリフラグ
    145         */
    146        @Override
    147        public abstract boolean isDirectory();
    148
    149        /**
    150         * 一覧取得
    151         *
    152         * パスのファイルと、ディレクトリ一覧を取得します。
    153         * @praram filter ファイルフィルタ
    154         * @return ファイルとティレクトリ一覧
    155         */
    156        @Override
    157        public abstract File[] listFiles(FileFilter filter);
    158
    159        /**
    160         * 親ディレクトリの取得
    161         *
    162         * 親のディレクトリ情報を返します。
    163         *
    164         * @return 親のディレクトリ
    165         */
    166        @Override
    167        public abstract File getParentFile();
    168
    169        /**
    170         * ファイルパス取得
    171         *
    172         * ファイルパスを取得します。
    173         *
    174         * @return 設定パス
    175         */
    176        @Override
    177        public String getPath() {
    178                return conPath;
    179        }
    180
    181        /**
    182         * 絶対パス取得
    183         *
    184         * 絶対パスを取得します。
    185         *
    186         * @return 絶対パス
    187         */
    188        @Override
    189        public String getAbsolutePath() {
    190                return conPath;
    191        }
    192
    193        /**
    194         * ファイル名取得
    195         *
    196         * ファイル名を取得します。
    197         *
    198         * @return 名称
    199         */
    200        @Override
    201        public String getName() {
    202                return drawName(conPath);
    203        }
    204
    205        /**
    206         * 親のパス取得
    207         *
    208         * 親のパスを取得します。
    209         *
    210         * @return 親のパス
    211         */
    212        @Override
    213        public String getParent() {
    214                return drawParent(conPath);
    215        }
    216
    217        /**
    218         * ファイル移動
    219         *
    220         * ファイルを指定先に移動します。
    221         *
    222         * @param afPath 移動先
    223         * @return 成否フラグ
    224         */
    225        @Override
    226        public boolean move(final String afPath) {
    227                boolean flgRtn = false;
    228
    229                flgRtn = copy(afPath);
    230                if (flgRtn) {
    231                        flgRtn = delete();
    232                }
    233
    234                return flgRtn;
    235        }
    236
    237        /**
    238         * 存在チェック
    239         *
    240         * 存在する場合は、trueを返します。
    241         *
    242         * @return 存在フラグ
    243         */
    244        @Override
    245        public boolean exists() {
    246                return isDirectory() | isFile();
    247        }
    248
    249        /**
    250         * ディレクトリの作成
    251         *
    252         * ※１つのディレクトリのみ作成します。
    253         * クラウドストレージにはディレクトリの概念が無いため、
    254         * 作成は行わず、trueを返します。
    255         *
    256         * @return 成否フラグ
    257         */
    258        @Override
    259        public boolean mkdir() {
    260                return true;
    261        }
    262
    263        /**
    264         * ディレクトリの作成(複数)
    265         *
    266         * ※複数のディレクトリを作成します。
    267         * クラウドストレージにはディレクトリの概念が無いため、
    268         * 作成は行わず、trueを返します。
    269         *
    270         *
    271         * @return 成否フラグ
    272         */
    273        @Override
    274        public boolean mkdirs() {
    275                return true;
    276        }
    277
    278        /**
    279         * ファイル名変更
    280         *
    281         * 指定のファイル情報のファイル名に変更します。
    282         *
    283         * @param dest 変更後のファイル情報
    284         * @return 成否フラグ
    285         */
    286        @Override
    287        public boolean renameTo(final File dest) {
    288                return move(dest.getPath());
    289        }
    290
    291        /**
    292         * 書き込み可能フラグ
    293         *
    294         * ※クラウドストレージの場合は、
    295         * 存在すればtrueを返します。
    296         *
    297         * @return 書き込み可能フラグ
    298         */
    299        @Override
    300        public boolean canWrite() {
    301                return exists();
    302        }
    303
    304        /**
    305         * 読み取り可能フラグ
    306         *
    307         * ※クラウドストレージの場合は、
    308         * 存在すればtrueを返します。
    309         *
    310         * @return 読み取り可能フラグ
    311         */
    312        @Override
    313        public boolean canRead() {
    314                return exists();
    315        }
    316
    317        /**
    318         * 隠しファイルフラグ
    319         *
    320         * ※クラウドストレージの場合は、
    321         * 必ずfalseを返します。
    322         *
    323         * @return 隠しファイルフラグ
    324         */
    325        @Override
    326        public boolean isHidden() {
    327                return false;
    328        }
    329
    330        /**
    331         * 新規ファイル作成
    332         *
    333         * 既にファイルが存在しない場合のみ、
    334         * 空のファイルを作成します。
    335         *
    336         * @return 成否フラグ
    337         * @throws IOException ファイル関連エラー情報
    338         */
    339        @Override
    340        public boolean createNewFile() throws IOException {
    341                boolean rtn = false;
    342
    343                if (!exists()) {
    344                        InputStream is = null;
    345                        try {
    346                                is = new ByteArrayInputStream(new byte[0]);
    347                                write(is);
    348                                rtn = true;
    349                        } finally {
    350                                Closer.ioClose(is);
    351                        }
    352                }
    353
    354                return rtn;
    355        }
    356
    357        /**
    358         * 最終更新時刻の更新
    359         *
    360         * 最終更新時刻の更新を行います。
    361         * ※クラウドストレージの場合は、
    362         * 最終更新時刻の更新を行えません。
    363         *
    364         * @param time 更新する最終更新時刻
    365         * @return 成否フラグ
    366         */
    367        @Override
    368        public boolean setLastModified(final long time) {
    369                // クラウドストレージでは、setLastModifiedによる、
    370                // 最終更新時刻の設定はできないので、
    371                // 処理を行わずにtrueを返します。
    372                return true;
    373        }
    374
    375        /**
    376         * カノニカルファイル情報の取得
    377         *
    378         * ※ローカルサーバのみ通常ファイルと、
    379         * カノニカルファイルで異なります。
    380         *
    381         * @return カノニカルファイル情報
    382         * @throws IOException ファイル関連エラー情報
    383         */
    384        @Override
    385        public FileOperation getCanonicalFile() throws IOException {
    386                return this;
    387        }
    388
    389        /**
    390         * toString
    391         *
    392         * パスを返します。
    393         *
    394         * @return ファイルパス
    395         */
    396        @Override
    397        public String toString() {
    398                return conPath;
    399        }
    400
    401        /** 共通関数 **/
    402        /**
    403         * ファイルパスの編集
    404         *
    405         * パスの先頭が「/」の場合は「/」の除去と、「//」を「/」に置換処理の追加。
    406         *
    407         * @og.rev 5.10.12.2 (2019/06/17) 相対パス対応
    408         *
    409         * @param path ファイルパス
    410         * @return 変更後パス
    411         */
    412        protected String editPath(final String path) {
    413                if (StringUtil.isNull(path)) {
    414                        return "";
    415                }
    416                String rtn = path;
    417
    418                // 「//+」は「/」に置換
    419                rtn = rtn.replaceAll("//+", "/");
    420                // 先頭が「/」の場合は除去
    421//              if ("/".equals(rtn.substring(0, 1))) {
    422                if( FS == rtn.charAt(0) ) {
    423                        rtn = rtn.substring(1);
    424                }
    425                // 後尾の「.」は除去
    426                rtn = rTrim(rtn, '.');
    427                // 後尾の「/」は除去
    428                rtn = rTrim(rtn, FS);
    429
    430                // 5.10.12.2 (2019/06/17)
    431                // 「../」の文字列は１つ上のディレクトリに変換を行います。
    432                Matcher m = ptnPreDir.matcher(rtn);
    433
    434                // 「../」が無くなるまで、１つずづ変換します。
    435                while(m.find()) {
    436                        rtn = m.replaceFirst("");
    437                        m = ptnPreDir.matcher(rtn);
    438                }
    439
    440                return rtn;
    441        }
    442
    443        /**
    444         * 親のパスを抽出
    445         *
    446         * キーから親のパスを抽出します。
    447         *
    448         * @param key キー
    449         * @return 親のパス
    450         */
    451        protected String drawParent(final String key) {
    452                final int k = key.lastIndexOf(FS);
    453
    454                String rtn = "";
    455                if (k > 0) {
    456                        rtn = key.substring(0, key.lastIndexOf(FS));
    457                }
    458                if ("/".equals(File.separator)) {
    459                        rtn = File.separator + rtn;
    460                }
    461
    462                return rtn;
    463        }
    464
    465        /**
    466         * 名称の抽出
    467         *
    468         * 引数のkeyから名称を抽出します。
    469         *
    470         * @param key キー(パス)
    471         * @return 名称
    472         */
    473        protected String drawName(final String key) {
    474                final int k = key.lastIndexOf(FS);
    475
    476                String rtn = key;
    477                if (k > 0) {
    478                        rtn = key.substring(key.lastIndexOf(FS) + 1);
    479                }
    480                return rtn;
    481        }
    482
    483        /**
    484         * ディレクトリ用のパス編集
    485         *
    486         * 後尾に「/」がない場合は、付与します。
    487         *
    488         * @param path パス
    489         * @return 後尾に「/」ありのパス
    490         */
    491        protected String setDirTail(final String path) {
    492                if (StringUtil.isNull(path)) {
    493                        return path;
    494                }
    495
    496                final StringBuilder sb = new StringBuilder(path);
    497//              if (!"/".equals(path.substring(path.length() - 1))) {
    498                if ( FS != path.charAt(path.length() - 1) ) {
    499                        sb.append(FS);
    500                }
    501                return sb.toString();
    502        }
    503
    504        /**
    505         * 右側トリム処理
    506         *
    507         * 右側の文字が、指定の文字の場合、除去します。
    508         *
    509         * @param str 対象文字列
    510         * @param chr 指定文字
    511         * @return 右側から指定文字を除去後の文字列
    512         */
    513        protected String rTrim(final String str, final char chr) {
    514                String rtn = str;
    515                int trgPos = 0;
    516                for (int i = str.length() - 1; i >= 0; i--) {
    517                        if (str.charAt(i) == chr) {
    518                                trgPos = i;
    519                                // すべて合致した場合は、から文字を返す
    520                                if (trgPos == 0) {
    521                                        rtn = "";
    522                                }
    523                        } else {
    524                                break;
    525                        }
    526                }
    527
    528                if (trgPos > 0) {
    529                        rtn = str.substring(0, trgPos);
    530                }
    531
    532                return rtn;
    533        }
    534
    535        /**
    536         * ファイル区切り文字変換
    537         *
    538         * ファイル区切り文字を変換します。
    539         *
    540         * @param path 変換前文字列
    541         * @return 返還後文字列
    542         */
    543        protected String replaceFileSeparetor(final String path) {
    544                if (StringUtil.isNull(path)) {
    545                        return "";
    546                }
    547
    548                return path.replaceAll("\\\\", "/");
    549        }
    550
    551        /**
    552         * フィルター処理
    553         *
    554         * フィルター処理を行います。
    555         *
    556         * @param list フィルタを行うリスト
    557         * @param filter フィルタ情報
    558         * @return フィルタ後のリスト
    559         */
    560        protected File[] filter(final List<File> list, final FileFilter filter) {
    561                final List<File> files = new ArrayList<File>();
    562                for (final File file : list) {
    563                        if (filter.accept(file)) {
    564                                files.add(file);
    565                        }
    566                }
    567                return files.toArray(new File[files.size()]);
    568        }
    569
    570        /**
    571         * ストリームの変換処理
    572         *
    573         * InputStreamをbyte[]に変換。
    574         * InputStreamのサイズ計算に利用。
    575         *
    576         * @param is byte配列に変換するInputStream
    577         * @return InpusStreamをbyte配列に変換した値
    578         * @throws IOException ファイル関連エラー情報
    579         */
    580        protected byte[] toByteArray(final InputStream is) throws IOException {
    581                final ByteArrayOutputStream output = new ByteArrayOutputStream();
    582                try {
    583                        final byte[] b = new byte[BUFFER_SIZE];
    584                        int n = 0;
    585                        while ((n = is.read(b)) != -1) {
    586                                output.write(b, 0, n);
    587                        }
    588                        return output.toByteArray();
    589                } finally {
    590                        output.close();
    591                }
    592        }
    593
    594        /**
    595         * ローカル実行フラグ判定
    596         *
    597         * このabstract クラスの継承クラスはクラウド上で実行されるため、
    598         * falseを返します。
    599         *
    600         * @return ローカル実行フラグ
    601         */
    602        @Override
    603        public boolean isLocal() {
    604                return false;
    605        }
    606
    607        /** java.io.Fileに実装されており、クラウド用ファイルクラスに未実装のメソッドの対応 */
    608        /**
    609         * canExecuteの実行
    610         *
    611         * クラウド側では未実装のメソッドです。
    612         *
    613         * @return フラグ
    614         */
    615        @Override
    616        public boolean canExecute() {
    617                throw new RuntimeException(UNIMPLEMNTED_ERR);
    618        }
    619
    620        /**
    621         * deleteOnExitの実行
    622         *
    623         * クラウド側では未実装のメソッドです。
    624         *
    625         */
    626        @Override
    627        public void deleteOnExit() {
    628                throw new RuntimeException(UNIMPLEMNTED_ERR);
    629        }
    630
    631        /**
    632         * getAbsoluteFileの実行
    633         *
    634         * クラウド側では未実装のメソッドです。
    635         *
    636         * @return ファイル
    637         */
    638        @Override
    639        public File getAbsoluteFile() {
    640                throw new RuntimeException(UNIMPLEMNTED_ERR);
    641        }
    642
    643        /**
    644         * getFreeSpaceの実行
    645         *
    646         * クラウド側では未実装のメソッドです。
    647         *
    648         * @return 数値
    649         */
    650        @Override
    651        public long getFreeSpace() {
    652                throw new RuntimeException(UNIMPLEMNTED_ERR);
    653        }
    654
    655        /**
    656         * getTotalSpaceの実行
    657         *
    658         * クラウド側では未実装のメソッドです。
    659         *
    660         * @return 数値
    661         */
    662        @Override
    663        public long getTotalSpace() {
    664                throw new RuntimeException(UNIMPLEMNTED_ERR);
    665        }
    666
    667        /**
    668         * getUsableSpaceの実行
    669         *
    670         * クラウド側では未実装のメソッドです。
    671         *
    672         * @return 数値
    673         */
    674        @Override
    675        public long getUsableSpace() {
    676                throw new RuntimeException(UNIMPLEMNTED_ERR);
    677        }
    678
    679        /**
    680         * isAbsoluteの実行
    681         *
    682         * クラウド側では未実装のメソッドです。
    683         *
    684         * @return フラグ
    685         */
    686        @Override
    687        public boolean isAbsolute() {
    688                throw new RuntimeException(UNIMPLEMNTED_ERR);
    689        }
    690
    691        /**
    692         * setReadableの実行
    693         *
    694         * クラウド側では未実装のメソッドです。
    695         *
    696         * @param readable フラグ
    697         * @return フラグ
    698         */
    699        @Override
    700        public boolean setReadable(final boolean readable) {
    701                throw new RuntimeException(UNIMPLEMNTED_ERR);
    702        }
    703
    704        /**
    705         * setReadableの実行
    706         *
    707         * クラウド側では未実装のメソッドです。
    708         *
    709         * @param readable フラグ
    710         * @param ownerOnly フラグ
    711         * @return フラグ
    712         */
    713        @Override
    714        public boolean setReadable(final boolean readable, final boolean ownerOnly) {
    715                throw new RuntimeException(UNIMPLEMNTED_ERR);
    716        }
    717
    718        /**
    719         * setWritableの実行
    720         *
    721         * クラウド側では未実装のメソッドです。
    722         *
    723         * @param writable フラグ
    724         * @return フラグ
    725         */
    726        @Override
    727        public boolean setWritable(final boolean writable) {
    728                throw new RuntimeException(UNIMPLEMNTED_ERR);
    729        }
    730
    731        /**
    732         * canExecuteの実行
    733         *
    734         * クラウド側では未実装のメソッドです。
    735         *
    736         * @param writable フラグ
    737         * @param ownerOnly フラグ
    738         * @return フラグ
    739         */
    740        @Override
    741        public boolean setWritable(final boolean writable, final boolean ownerOnly) {
    742                throw new RuntimeException(UNIMPLEMNTED_ERR);
    743        }
    744
    745        /**
    746         * canExecuteの実行
    747         *
    748         * クラウド側では未実装のメソッドです。
    749         *
    750         * @return URI情報
    751         */
    752        @Override
    753        public URI toURI() {
    754                throw new RuntimeException(UNIMPLEMNTED_ERR);
    755        }
    756}