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) 相対パス対応「../」と1つ前のディレクトリ情報を抽出(1つ前が先頭の場合は、/ではなく^)
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         * ※1つのディレクトリのみ作成します。
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                // 「../」の文字列は1つ上のディレクトリに変換を行います。
432                Matcher m = ptnPreDir.matcher(rtn);
433                
434                // 「../」が無くなるまで、1つずづ変換します。
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}