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