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.hayabusa.taglib;
017
018import org.opengion.hayabusa.common.HybsSystem;
019import org.opengion.hayabusa.common.HybsSystemException;
020import org.opengion.hayabusa.db.DBTableModel;
021import org.opengion.fukurou.util.ErrorMessage;
022import org.opengion.fukurou.util.FileUtil;
023import org.opengion.fukurou.util.ToString;                                              // 6.1.1.0 (2015/01/17)
024import org.opengion.fukurou.util.ArraySet;                                              // 6.4.3.4 (2016/03/11)
025
026import static org.opengion.fukurou.util.StringUtil.nval ;
027import static org.opengion.fukurou.system.HybsConst.BR;                 // 6.1.0.0 (2014/12/26) refactoring
028
029import java.util.Locale ;
030import java.util.Set ;
031import java.util.TreeSet ;
032import java.util.Comparator ;
033import java.io.File ;
034import java.io.Serializable;
035
036/**
037 * ファイル検索リストを元に、action に基づいた処理を行うタグです。
038 * command="ENTRY" 時のみ処理を行います。
039 *
040 * fileQuery などで検索したファイル一覧のDBTableModel を元に、ファイルの
041 * コピー(COPY)、移動(MOVE,MODIFY)、削除(DELETE)などの処理を行います。
042 * 処理を行うオリジナルファイルは、PARENT,NAME というカラムでなければなりません。
043 * このカラム名は、fileQuery の検索時には、必ず作成されるカラムです。
044 * また、各アクションに対応するターゲットファイルは、TO_PARENT,TO_NAME という
045 * カラムで指定するか、targetDir 属性を利用してフォルダを指定します。
046 * TO_PARENT(先フォルダ)と、TO_NAME(先ファイル名)は、処理に応じて、必要なカラムが
047 * あれば、自動的に処理します。
048 * つまり、TO_PARENT のみの場合は、ファイル名はオリジナルのまま、フォルダのみ変更します。
049 * 逆に、TO_NAME の場合は、フォルダはそのままで、ファイル名のみ指定します。
050 * 両方同時に指定することも可能です。
051 * targetDir 属性で指定する場合は、TO_PARENT のみに同じ値を設定した場合と同じになります。
052 * この属性を指定すると、TO_PARENT は無視されます。(TO_NAME は有効です。)
053 * COPY、MOVE(,MODIFY) の場合は、指定のフォルダに一括処理可能です。
054 * COPY、MOVE(,MODIFY) などの処理で、ターゲットフォルダが存在しないときに、作成するか、エラーにするかは
055 * createDir属性 で指定できます。初期値は、(true:作成する) です。
056 * これは、COPY先やMOVE(,MODIFY)先が存在している前提のシステムで、不要な箇所に間違ってフォルダを
057 * 自動作成されると困る場合に、(false:作成しない) とすれば、間違いに気づく確率が上がります。
058 *
059 * ※ このタグは、Transaction タグの対象ではありません。
060 *
061 * @og.formSample
062 * ●body:なし
063 * ●形式:
064 *      ・<og:fileUpdate
065 *          action        = "COPY|MOVE|MODIFY|DELETE" アクション属性(必須)
066 *          command       = "[ENTRY]"                 ENTRY 時のみ実行します(初期値:ENTRY)
067 *          targetDir     = "[指定フォルダ]"          ターゲットとなるフォルダ
068 *          createDir     = "[true/false]"            ターゲットとなるフォルダがなければ作成する(true)かどうか(初期値:true)
069 *          tableId       = [HybsSystem.TBL_MDL_KEY]  DBTableModel を取り出すキー
070 *          outMessage    = "[true/false]"            検索結果のメッセージを表示する(true)かどうかを指定(初期値:true)
071 *          displayMsg    = "MSG0040";                処理結果を表示します(初期値:「 件登録しました。」)
072 *          selectedAll   = "[false/true]"            データを全件選択済みとして処理する(true)かどうか指定(初期値:false)
073 *          keepTimeStamp = "[false/true]"            COPY,親違いMOVE(,MODIFY)の時にオリジナルのタイムスタンプを使用するかどうか(初期値:false)
074 *      />
075 *
076 *    [action属性(必須)]
077 *      COPY   オリジナルファイルを、ターゲット(TO_PARENT,TO_NAMEで指定)にコピーします。
078 *      MOVE   オリジナルファイルを、ターゲットに移動(COPY+DELETE)/名称変更(RENAME)します。
079 *      MODIFY (MOVE と同じ。エンジンの command を利用するための簡易action)
080 *      DELETE オリジナルファイルを削除します(ターゲット(TO_PARENT,TO_NAME)は、関係しません)。
081 *
082 * ●Tag定義:
083 *   <og:fileUpdate
084 *       action           ○【TAG】アクション[COPY|MOVE|MODIFY|DELETE]をセットします(必須)。
085 *       command            【TAG】コマンド[ENTRY]をセットします(初期値:ENTRY)
086 *       targetDir          【TAG】ターゲットとなるフォルダを指定します
087 *       createDir          【TAG】ターゲットとなるフォルダがなければ、作成するかどうかを指定します(初期値:true)
088 *       tableId            【TAG】(通常は使いません)結果のDBTableModelを、sessionに登録するときのキーを指定します
089 *       scope              【TAG】キャッシュする場合のスコープ[request/page/session/application]を指定します(初期値:session)
090 *       outMessage         【TAG】検索結果のメッセージを表示する/しない[true/false]を指定します(初期値:true)
091 *       displayMsg         【TAG】処理結果を画面上に表示するメッセージリソースIDを指定します(初期値:MSG0040[ 件登録しました])
092 *       selectedAll        【TAG】データを全件選択済みとして処理するかどうか[true/false]を指定します(初期値:false)
093 *       keepTimeStamp      【TAG】オリジナルのタイムスタンプを利用するかどうかを指定します(初期値:false)
094 *       inPath             【TAG】入力共通パスを指定します(PARENTフォルダの共通部分、COPY/MOVE時にtargetDirと置換されます) 6.8.0.0 (2017/06/02)。
095 *       useTimeView        【TAG】処理時間を表示する TimeView を表示するかどうかを指定します
096 *                                                                              (初期値:VIEW_USE_TIMEBAR[={@og.value SystemData#VIEW_USE_TIMEBAR}])。
097 *       caseKey            【TAG】このタグ自体を利用するかどうかの条件キーを指定します(初期値:null)
098 *       caseVal            【TAG】このタグ自体を利用するかどうかの条件値を指定します(初期値:null)
099 *       caseNN             【TAG】指定の値が、null/ゼロ文字列 でない場合(Not Null=NN)は、このタグは使用されます(初期値:判定しない)
100 *       caseNull           【TAG】指定の値が、null/ゼロ文字列 の場合は、このタグは使用されます(初期値:判定しない)
101 *       caseIf             【TAG】指定の値が、true/TRUE文字列の場合は、このタグは使用されます(初期値:判定しない)
102 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
103 *   />
104 *
105 * ●使用例
106 *       ・<og:fileUpdate command="{@command}" action="COPY" />
107 *             TO_PARENT または、 TO_NAME(両方指定も可)による行単位 COPY 処理
108 *             fileQuery の useUpdateClm="true" を設定し、検索結果に、TO_PARENT、 TO_NAMEカラムを追加します。
109 *             TO_PARENT または、 TO_NAME は、columnSet などで値をセットしておきます。
110 *
111 *       ・<og:fileUpdate command="{@command}" action="MODIFY" targetDir="AAA_DIR"  />
112 *             fileQuery の検索結果を、AAA_DIR フォルダに移動します。
113 *             ファイル名は、そのままオリジナルの値が使用されます。
114 *
115 * @og.rev 5.3.4.0 (2011/04/01) 新規追加
116 * @og.group ファイル出力
117 *
118 * @version  4.0
119 * @author       Kazuhiko Hasegawa
120 * @since    JDK5.0,
121 */
122public class FileUpdateTag extends CommonTagSupport {
123        /** このプログラムのVERSION文字列を設定します。   {@value} */
124        private static final String VERSION = "6.8.0.0 (2017/06/02)" ;
125        private static final long serialVersionUID = 680020170602L ;
126
127        /** command 引数に渡す事の出来る コマンド  登録{@value} */
128        public static final String CMD_ENTRY  = "ENTRY" ;
129        /** command 引数に渡す事の出来る コマンド リスト  */
130        // 6.4.3.4 (2016/03/11) String配列 から、Setに置き換えます。
131        private static final Set<String> COMMAND_SET = new ArraySet<>( CMD_ENTRY );
132
133        /** エラーメッセージID {@value} */
134        private static final String ERR_MSG_ID  = HybsSystem.ERR_MSG_KEY;               // 6.4.1.1 (2016/01/16) errMsgId → ERR_MSG_ID  refactoring
135
136        /** action 引数に渡す事の出来る アクションコマンド  COPY {@value} */
137        public static final String ACT_COPY             = "COPY" ;
138        /** action 引数に渡す事の出来る アクションコマンド  MOVE {@value} */
139        public static final String ACT_MOVE             = "MOVE" ;
140        /** action 引数に渡す事の出来る アクションコマンド  MODIFY {@value} */
141        public static final String ACT_MODIFY           = "MODIFY" ;
142        /** action 引数に渡す事の出来る アクションコマンド  DELETE {@value} */
143        public static final String ACT_DELETE   = "DELETE" ;
144        // 6.4.3.4 (2016/03/11) String配列 から、Setに置き換えます。
145        private static final Set<String> ACTION_SET = new ArraySet<>( ACT_COPY , ACT_MOVE , ACT_MODIFY , ACT_DELETE );
146
147        private String  action          ;
148        private String  targetDir       ;                       // ターゲットとなるフォルダ
149        private boolean createDir       = true;         // ターゲットとなるフォルダがなければ、作成するかどうか(true:作成する)
150
151        private String  inPath          ;                       // 6.8.0.0 (2017/06/02) 入力共通パスを指定します。
152
153        private String  tableId         = HybsSystem.TBL_MDL_KEY;
154        private String  command         = CMD_ENTRY;
155        private boolean outMessage      = true;
156        private String  displayMsg      = "MSG0040";            //  件登録しました。
157        private boolean selectedAll ;
158        private boolean keepTimeStamp;                                  // オリジナルのタイムスタンプを利用する場合、true
159
160        private transient DBTableModel  table           ;
161        private transient ErrorMessage  errMessage      ;
162        private int             executeCount    = -1;                   // 処理件数
163        private int             errCode                 = ErrorMessage.OK;
164        private boolean useTimeView             = HybsSystem.sysBool( "VIEW_USE_TIMEBAR" );             // 6.3.6.0 (2015/08/16)
165
166        /**
167         * デフォルトコンストラクター
168         *
169         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
170         */
171        public FileUpdateTag() { super(); }             // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
172
173        /**
174         * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
175         *
176         * @og.rev 6.4.4.1 (2016/03/18) 意味のない、StringBuilderだったので、廃止します。
177         *
178         * @return      後続処理の指示
179         */
180        @Override
181        public int doEndTag() {
182                debugPrint();
183                // 5.2.2.0 (2010/11/01) caseKey 、caseVal 属性対応
184                if( !useTag() ) { return EVAL_PAGE ; }
185
186                final long dyStart = System.currentTimeMillis();
187
188                table = (DBTableModel)getObject( tableId );
189
190                String label  = "";                             // 4.0.0 (2005/11/30) 検索しなかった場合。
191                if( table != null && table.getRowCount() > 0 && check( command, COMMAND_SET ) ) {
192                        startQueryTransaction( tableId );
193
194                        execute();      // 実際の処理を実行します。
195
196                        setRequestAttribute( "DB.COUNT"   , String.valueOf( executeCount ) );
197                        setRequestAttribute( "DB.ERR_CODE", String.valueOf( errCode ) );
198
199                        final String err = TaglibUtil.makeHTMLErrorTable( errMessage,getResource() );
200                        if( err != null && err.length() > 0 ) {
201                                label = err ;           // 6.4.4.1 (2016/03/18)
202                                setSessionAttribute( ERR_MSG_ID,errMessage );
203                        }
204
205                        if( table != null && ! commitTableObject( tableId, table ) ) {
206                                jspPrint( "FileUpdateTag Query処理が割り込まれました。DBTableModel は登録しません。" );
207                                return SKIP_PAGE ;
208                        }
209                }
210
211                jspPrint( label );
212
213                // 実行件数の表示
214                // 4.0.0 (2005/11/30) 出力順の変更。一番最初に出力します。
215                if( displayMsg != null && displayMsg.length() > 0 ) {
216                        final String status = executeCount + getResource().getLabel( displayMsg ) ;
217                        jspPrint( status + BR );
218                }
219
220                if( useTimeView ) {             // 6.3.6.0 (2015/08/16)
221                        // 3.5.4.7 (2004/02/06)
222                        final long dyTime = System.currentTimeMillis()-dyStart;
223                        jspPrint( "<div id=\"queryTime\" value=\"" + (dyTime) + "\"></div>" );  // 3.5.6.3 (2004/07/12)
224                }
225                return EVAL_PAGE ;
226        }
227
228        /**
229         * タグリブオブジェクトをリリースします。
230         * キャッシュされて再利用されるので、フィールドの初期設定を行います。
231         *
232         * @og.rev 6.8.0.0 (2017/06/02) 入力共通パス(inPath)を指定します。
233         */
234        @Override
235        protected void release2() {
236                super.release2();
237                tableId         = HybsSystem.TBL_MDL_KEY;
238                command         = CMD_ENTRY;
239                action          = null;
240                targetDir       = null;         // ターゲットとなるフォルダ
241                createDir       = true;         // ターゲットとなるフォルダがなければ、作成するかどうか(true:作成する)
242                outMessage      = true;
243                displayMsg      = "MSG0040";    //  件登録しました。
244                selectedAll = false;
245                keepTimeStamp = false;          // オリジナルのタイムスタンプを利用する場合、true
246                table           = null;
247                errMessage      = null;
248                executeCount= -1;               // 処理件数
249                errCode         = ErrorMessage.OK;
250                useTimeView     = HybsSystem.sysBool( "VIEW_USE_TIMEBAR" );     // 6.3.6.0 (2015/08/16)
251                inPath          = null;         // 6.8.0.0 (2017/06/02) 入力共通パスを指定します。
252        }
253
254        /**
255         * 処理を実行します。
256         *
257         * @og.rev 6.8.0.0 (2017/06/02) 入力共通パス(inPath)を指定します。
258         */
259        private void execute() {
260                final int[] rowNo = getParameterRows();
261                if( rowNo.length > 0 ) {
262
263                        final FromToFiles fromToFiles = new FromToFiles( table , targetDir , createDir , inPath );      // 6.8.0.0 (2017/06/02) 入力共通パス(inPath)を指定
264
265                        if( ACT_COPY.equalsIgnoreCase( action ) ) {
266                                actionCOPY( rowNo,fromToFiles );
267                        }
268                        // ACT_MODIFY は、エンジンの command で使うため、便利
269                        else if( ACT_MOVE.equalsIgnoreCase( action ) || ACT_MODIFY.equalsIgnoreCase( action ) ) {
270                                actionMOVE( rowNo,fromToFiles );
271                        }
272                        else if( ACT_DELETE.equalsIgnoreCase( action ) ) {
273                                actionDELETE( rowNo,fromToFiles );
274                        }
275                }
276        }
277
278        /**
279         * COPY アクションを実行します。
280         *
281         * @og.rev 5.6.5.2 (2013/06/21) From側がファイルの場合のみ処理します。
282         *
283         * @param       rowNo           処理を実施する行番号
284         * @param       fromToFiles     FromFile,ToFileをまとめた補助クラス
285         * @throws      HybsSystemException     処理中に何らかのエラーが発生した場合
286         */
287        private void actionCOPY( final int[] rowNo , final FromToFiles fromToFiles ) {
288                File fromFile = null ;
289                File toFile   = null ;
290
291                executeCount = 0 ;      // 開始前に初期化しておく。
292                final int rowCount = rowNo.length ;
293                for( int i=0; i<rowCount; i++ ) {
294                        final File[] files = fromToFiles.makeFromToFile( rowNo[i] );    // FromFile,ToFile
295                        fromFile = files[0];
296                        toFile   = files[1];
297
298                        // 5.6.5.2 (2013/06/21) From側がファイルの場合のみ処理します。
299                        if( fromFile.isFile() && !FileUtil.copy( fromFile,toFile,keepTimeStamp ) ) {
300                                final String errMsg = "アクション=[" + action + "]中にエラーが発生しました。" + CR
301                                                                        + "From=[" + fromFile + "],To=[" + toFile + "]" + CR;
302                                // 6.0.2.5 (2014/10/31) refactoring
303                                if( errMessage == null ) { errMessage = new ErrorMessage( "FileUpdateTag Error" ); }
304                                errMessage.addMessage( rowNo[i]+1,ErrorMessage.NG,"actionCOPY",errMsg );
305
306                        }
307                        executeCount++ ;
308                }
309        }
310
311        /**
312         * MOVE アクションを実行します。
313         *
314         * @og.rev 5.5.2.4 (2012/05/16) メソッドの戻り値の設定
315         * @og.rev 5.6.5.2 (2013/06/21) From側がファイルの場合のみ処理します。
316         *
317         * @param       rowNo           処理を実施する行番号
318         * @param       fromToFiles     FromFile,ToFileをまとめた補助クラス
319         * @throws      HybsSystemException     処理中に何らかのエラーが発生した場合
320         */
321        private void actionMOVE( final int[] rowNo , final FromToFiles fromToFiles ) {
322                File fromFile = null ;
323                File toFile   = null ;
324
325                executeCount = 0 ;      // 開始前に初期化しておく。
326                final int rowCount = rowNo.length ;
327                for( int i=0; i<rowCount; i++ ) {
328                        final File[] files = fromToFiles.makeFromToFile( rowNo[i] );    // FromFile,ToFile
329                        fromFile = files[0];
330                        toFile   = files[1];
331
332                        if( fromToFiles.lastParentEquals() ) {  // FromDirとToDirが同じなので、RENAMEできる。
333                                if( !fromFile.renameTo( toFile ) ) {
334                                        final String errMsg = "アクション=[" + action + "]中にエラーが発生しました。" + CR
335                                                                                + "同一親フォルダのため、RENAME処理を行っています。" + CR
336                                                                                + "From=[" + fromFile + "],To=[" + toFile + "]" + CR;
337                                        // 6.0.2.5 (2014/10/31) refactoring
338                                        if( errMessage == null ) { errMessage = new ErrorMessage( "FileUpdateTag Error" ); }
339                                        errMessage.addMessage( rowNo[i]+1,ErrorMessage.NG,"actionMOVE",errMsg );
340                                }
341                        }
342                        // 5.6.5.2 (2013/06/21) From側がファイルの場合のみ処理します。
343                        else if( fromFile.isFile() ) {                  // FromDirとToDirが異なるので、COPY + DELETE する。
344                                if( !FileUtil.copy( fromFile,toFile,keepTimeStamp ) ) {
345                                        final String errMsg = "アクション=[" + action + "]中にエラーが発生しました。" + CR
346                                                                                + "移動前のCOPY処理を行っていました。" + CR
347                                                                                + "From=[" + fromFile + "],To=[" + toFile + "]" + CR;
348                                        // 6.0.2.5 (2014/10/31) refactoring
349                                        if( errMessage == null ) { errMessage = new ErrorMessage( "FileUpdateTag Error" ); }
350                                        errMessage.addMessage( rowNo[i]+1,ErrorMessage.NG,"actionMOVE",errMsg );
351                                }
352
353                                if( !fromFile.delete() ) {
354                                        String errMsg = "アクション=[" + action + "]中にエラーが発生しました。" + CR
355                                                                                + "移動後のオリジナルファイルの削除処理を行っていました。" + CR
356                                                                                + "From=[" + fromFile + "],To=[" + toFile + "]" + CR;
357                                        // 5.5.2.4 (2012/05/16) メソッドの戻り値の設定
358                                        if( !toFile.delete() ) {
359                                                errMsg = errMsg + "toFile も削除に失敗しました。" + CR;
360                                        }
361
362                                        // 6.0.2.5 (2014/10/31) refactoring
363                                        if( errMessage == null ) { errMessage = new ErrorMessage( "FileUpdateTag Error" ); }
364                                        errMessage.addMessage( rowNo[i]+1,ErrorMessage.NG,"actionMOVE",errMsg );
365                                }
366                        }
367                        executeCount++ ;
368                }
369        }
370
371        /**
372         * DELETE アクションを実行します。
373         *
374         * この処理では、リストにフォルダが含まれている場合も削除します。
375         * 通常、フォルダの削除は、その要素(内部)にファイル等が存在しない場合のみ
376         * 行いますが、検索リストから削除する順番によっては、フォルダもファイルも
377         * 削除対象になる場合があります。そこで、まず。ファイルだけ削除し、フォルダは、
378         * あとで削除するように処理を行います。
379         *
380         * @og.rev 5.6.5.2 (2013/06/21) フォルダも削除対象にします。
381         *
382         * @param       rowNo           処理を実施する行番号
383         * @param       fromToFiles     FromFile,ToFileをまとめた補助クラス
384         * @throws      HybsSystemException     処理中に何らかのエラーが発生した場合
385         */
386        private void actionDELETE( final int[] rowNo , final FromToFiles fromToFiles ) {
387                File fromFile = null;
388
389                // 5.6.5.2 (2013/06/21) フォルダを削除する為の、退避
390                final Set<File> dirSet = new TreeSet<>( new FileNameLengthComparator() );               // ファイルの文字数順に並べたSet
391
392                executeCount = 0 ;      // 開始前に初期化しておく。
393                final int rowCount = rowNo.length ;
394                for( int i=0; i<rowCount; i++ ) {
395                        fromFile = fromToFiles.makeFromOnly( rowNo[i] );        // FromFile
396
397                        // 5.6.5.2 (2013/06/21) まず、ファイルを削除します。
398                        if( fromFile.isFile() ) {
399                                if( !fromFile.delete() ) {
400                                        final String errMsg = "アクション=[" + action + "]中にエラーが発生しました。" + CR
401                                                                                + "From=[" + fromFile + "]" + CR;
402                                        // 6.0.2.5 (2014/10/31) refactoring
403                                        if( errMessage == null ) { errMessage = new ErrorMessage( "FileUpdateTag Error" ); }
404                                        errMessage.addMessage( rowNo[i]+1,ErrorMessage.NG,"actionDELETE",errMsg );
405                                }
406                        }
407                        else {
408                                // 5.6.5.2 (2013/06/21) フォルダの場合は、アドレスの桁数をキーにソートしておきます。
409                                dirSet.add( fromFile );
410                        }
411                        executeCount++ ;
412                }
413
414                // 5.6.5.2 (2013/06/21) フォルダの削除は、アドレスの桁数の大きい順(階層の深い順)に削除します。
415                for( final File file : dirSet ) {
416                        if( !file.delete() ) {
417                                final String errMsg = "アクション=[" + action + "]中にエラーが発生しました。" + CR
418                                                                        + "From(Dir)=[" + file + "]" + CR;
419                                // 6.0.2.5 (2014/10/31) refactoring
420                                if( errMessage == null ) { errMessage = new ErrorMessage( "FileUpdateTag Error" ); }
421                                errMessage.addMessage(-1,ErrorMessage.NG,"actionDELETE",errMsg );
422                        }
423                }
424        }
425
426        /**
427         * ファイルの名称の長さ順(長い順)に比較する、Comparator インターフェースの実装クラス
428         *
429         * ここでの大小比較は、ファイル名の文字数が、大きい方が、小さいとみなされます。
430         * つまり階層が深いので、先に処理する必要があるという事を意味します。
431         * 処理としては、f1 != null &amp;&amp; f2 != null で、len1 = f1.getAbsolutePath().length() と len2 = f2.getAbsolutePath().length() を比較し
432         * len1 &gt; len2 ⇒ 負 , len1 &lt; len2 ⇒ 正 , len1 == len2 ⇒ 0 を返します。
433         * 具体的には、return ( len2 - len1 ); です。
434         * 
435         * 注: このコンパレータは equals と一貫性のない順序付けを課します。
436         * 
437         * @og.rev 5.6.5.2 (2013/06/21) 新規追加
438         * 
439         */
440        private static final class FileNameLengthComparator implements Comparator<File> , Serializable {
441                private static final long serialVersionUID = 565220130621L ;            // 5.6.5.2 (2013/06/21)
442                /**
443                 * 順序付けのために 2つの引数を比較します。
444                 *
445                 * ここでの大小比較は、ファイル名の文字数が、大きい方が、小さいとみなされます。
446                 * 具体的には、return ( len2 - len1 ); です。
447                 * 
448                 * @param       f1 比較対象の1番目のオブジェクト
449                 * @param       f2 比較対象の2番目のオブジェクト
450                 * @return      最初の引数が2番目の引数より小さい場合は負の整数、両方が等しい場合は 0、最初の引数が 2番目の引数より大きい場合は正の整数
451                 */
452                public int compare( final File f1 , final File f2 ) {
453                        if( f1 == null || f2 == null ) {
454                                final String errMsg = "引数のFileにnullが含まれています。file1=[" + f1 + "] , file2=[" + f2 + "]" ;
455                                throw new IllegalArgumentException( errMsg );
456                        }
457
458                        final int len1 = f1.getAbsolutePath().length();
459                        final int len2 = f2.getAbsolutePath().length();
460
461                        return len2 - len1 ;
462                }
463        }
464
465        /**
466         * 表示データの HybsSystem.ROW_SEL_KEY を元に、選ばれた 行を処理の対象とします。
467         *
468         * @return      選択行の配列
469         * @og.rtnNotNull
470         */
471        @Override
472        protected int[] getParameterRows() {
473                final int[] rowNo ;
474                if( selectedAll ) {
475                        final int rowCnt = table.getRowCount();
476                        rowNo = new int[ rowCnt ];
477                        for( int i=0; i<rowCnt; i++ ) {
478                                rowNo[i] = i;
479                        }
480                } else {
481                        rowNo = super.getParameterRows();
482                }
483                return rowNo ;
484        }
485
486        /**
487         * 【TAG】アクション[COPY|MOVE|MODIFY|DELETE]をセットします。
488         *
489         * @og.tag
490         * アクションは、ファイルをコピー(COPY)したり、移動(MOVE,MODIFY)したり、削除(DELETE)する
491         * などの操作を指定する必須属性です。
492         *
493         * <table border="1" frame="box" rules="all" >
494         *   <caption>action属性(必須)のキーワード</caption>
495         *   <tr><th>action</th><th>名称</th><th>機能</th></tr>
496         *   <tr><td>COPY  </td><td>コピー</td><td>オリジナルファイルを、ターゲット(TO_PARENT,TO_NAMEで指定)にコピーします。</td></tr>
497         *   <tr><td>MOVE  </td><td>移動  </td><td>オリジナルファイルを、ターゲットに移動(COPY+DELETE)/名称変更(RENAME)します。</td></tr>
498         *   <tr><td>MODIFY</td><td>移動  </td><td>(MOVE と同じ。エンジンの command を利用するための簡易action)</td></tr>
499         *   <tr><td>DELETE</td><td>削除  </td><td>オリジナルファイルを、削除します。(フォルダ、ファイルに関わらず)</td></tr>
500         * </table>
501         *
502         * @og.rev 6.3.4.0 (2015/08/01) Arrays.toString から String.join に置き換え。
503         * @og.rev 6.4.3.4 (2016/03/11) String配列 から、Setに置き換えます。
504         *
505         * @param       act アクション (public static final 宣言されている文字列)
506         * @see         <a href="../../../../constant-values.html#org.opengion.hayabusa.taglib.FileUpdateTag.ACT_COPY">アクション定数</a>
507         */
508        public void setAction( final String act ) {
509                action = nval( getRequestParameter( act ),action );
510
511                if( action != null && !check( action, ACTION_SET ) ) {
512                        final String errMsg = "指定のアクションは実行できません。アクションエラー"       + CR
513                                                        + "action=[" + action + "] "                                                            + CR
514                                                        + "actionList=" + String.join( ", " , ACTION_SET ) ;
515                        throw new HybsSystemException( errMsg );
516                }
517        }
518
519        /**
520         * 【TAG】ターゲットとなるフォルダを指定します(初期値:null)。
521         *
522         * @og.tag
523         * targetDir 属性を利用する場合は、引数のファイル、またはフォルダが指定されたことに
524         * なります。COPY、MOVE(,MODIFY) の場合は、targetDir 属性にフォルダを指定することで一括処理可能です。
525         * 指定先のフォルダが存在しない場合は、createDir属性の値により処理が異なります。
526         * createDir="true"(初期値)で、ターゲットフォルダが存在しない場合は、自動作成します。
527         *
528         * @param  dir ターゲットとなるフォルダ
529         * @see         #setCreateDir( String )
530         */
531        public void setTargetDir( final String dir ) {
532                targetDir = nval( getRequestParameter( dir ),targetDir );
533        }
534
535        /**
536         * 【TAG】ターゲットとなるフォルダがなければ、作成するかどうかを指定します(初期値:true)。
537         *
538         * @og.tag
539         * COPY,MOVE(,MODIFY) などの処理で、ターゲットフォルダが存在しないときに、作成するか、エラーにするかを
540         * createDir属性 で指定できます。
541         * これは、COPY先やMOVE(,MODIFY)先が存在している前提のシステムで、不要な箇所に間違ってフォルダを
542         * 自動作成されると困る場合に、false:作成しない とすれば、間違いに気づく確率が上がります。
543         * 初期値は true:作成する です。
544         *
545         * @param       flag    フォルダ作成可否 [true:作成する/false:作成しない]
546         */
547        public void setCreateDir( final String flag ) {
548                createDir = nval( getRequestParameter( flag ),createDir );
549        }
550
551        /**
552         * 【TAG】(通常は使いません)結果のDBTableModelを、sessionに登録するときのキーを指定します
553         *              (初期値:HybsSystem#TBL_MDL_KEY[={@og.value HybsSystem#TBL_MDL_KEY}])。
554         *
555         * @og.tag
556         * 検索結果より、DBTableModelオブジェクトを作成します。これを、下流のviewタグ等に
557         * 渡す場合に、通常は、session を利用します。その場合の登録キーです。
558         * query タグを同時に実行して、結果を求める場合、同一メモリに配置される為、
559         * この tableId 属性を利用して、メモリ空間を分けます。
560         *              (初期値:HybsSystem#TBL_MDL_KEY[={@og.value HybsSystem#TBL_MDL_KEY}])。
561         *
562         * @param       id テーブルID (sessionに登録する時のID)
563         */
564        public void setTableId( final String id ) {
565                tableId = nval( getRequestParameter( id ),tableId );
566        }
567
568        /**
569         * 【TAG】コマンド (ENTRY)をセットします(初期値:ENTRY)。
570         *
571         * @og.tag
572         * このタグは、command="ENTRY" でのみ実行されます。
573         * コマンドは,HTMLから(get/post)指定されますので,CMD_xxx で設定される
574         * フィールド定数値のいづれかを、指定できます。
575         * 初期値は、ENTRY なので、何も指定しなければ、実行されます。
576         *
577         * @param       cmd コマンド (public static final 宣言されている文字列)
578         * @see         <a href="../../../../constant-values.html#org.opengion.hayabusa.taglib.FileUpdateTag.CMD_ENTRY">コマンド定数</a>
579         */
580        public void setCommand( final String cmd ) {
581                final String cmd2 = getRequestParameter( cmd );
582                if( cmd2 != null && cmd2.length() >= 0 ) { command = cmd2.toUpperCase(Locale.JAPAN); }
583        }
584
585        /**
586         * 【TAG】検索結果のメッセージを表示する/しない[true/false]を指定します(初期値:true)。
587         *
588         * @og.tag
589         * 初期値は、表示する:true です。
590         *
591         * @param       flag  メッセージ表示可否 [true:表示する/それ以外:含めない]
592         */
593        public void setOutMessage( final String flag ) {
594                outMessage = nval( getRequestParameter( flag ),outMessage );
595        }
596
597        /**
598         * 【TAG】処理結果を画面上に表示するメッセージリソースIDを指定します(初期値:MSG0040[ 件登録しました])。
599         *
600         * @og.tag
601         * ここでは、検索結果の件数や登録された件数をまず出力し、
602         * その次に、ここで指定したメッセージをリソースから取得して表示します。
603         * 表示させたくない場合は, displayMsg = "" をセットしてください。
604         * displayMsg の初期値は、MSG0040[ 件登録しました]です。
605         *
606         * @param       id 処理結果表示メッセージID
607         */
608        public void setDisplayMsg( final String id ) {
609                final String ids = getRequestParameter( id );
610                if( ids != null ) { displayMsg = ids; }
611        }
612
613        /**
614         * 【TAG】データを全件選択済みとして処理するかどうか[true/false]を指定します(初期値:false)。
615         *
616         * @og.tag
617         * 全てのデータを選択済みデータとして扱って処理します。
618         * 全件処理する場合に、(true/false)を指定します。
619         * 初期値は false です。
620         *
621         * @param  all 全件選択済み指定 [true:全件選択済み/false:通常]
622         */
623        public void setSelectedAll( final String all ) {
624                selectedAll = nval( getRequestParameter( all ),selectedAll );
625        }
626
627        /**
628         * 【TAG】オリジナルのタイムスタンプを利用するかどうかを指定します(初期値:false)。
629         *
630         * @og.tag
631         * COPYや親違いMOVE(,MODIFY)の時に、オリジナルのタイムスタンプをそのままコピー先のファイルにも
632         * 適用するかどうかを指定します。
633         * タイムスタンプを初期化されたくない場合に、true に設定します。
634         * 初期値は 利用しない:false です。
635         *
636         * @param  flag タイムスタンプ利用 [true:する/false:しない]
637         */
638        public void setKeepTimeStamp( final String flag ) {
639                keepTimeStamp = nval( getRequestParameter( flag ),keepTimeStamp );
640        }
641
642        /**
643         * 【TAG】処理時間を表示する TimeView を表示するかどうか[true:する/false:しない]を指定します
644         *              (初期値:VIEW_USE_TIMEBAR[={@og.value SystemData#VIEW_USE_TIMEBAR}])。
645         *
646         * @og.tag
647         * true に設定すると、処理時間を表示するバーイメージが表示されます。
648         * これは、DB検索、APサーバー処理、画面表示の各処理時間をバーイメージで
649         * 表示させる機能です。処理時間の目安になります。
650         * (初期値:VIEW_USE_TIMEBAR[={@og.value SystemData#VIEW_USE_TIMEBAR}])。
651         *
652         * @og.rev 6.3.6.0 (2015/08/16) useTimeView の初期値を、VIEW_USE_TIMEBAR にする。
653         *
654         * @param       flag    処理時間を表示 [true:する/false:しない]
655         */
656        public void setUseTimeView( final String flag ) {
657                useTimeView = nval( getRequestParameter( flag ),useTimeView );
658        }
659
660        /**
661         * 【TAG】入力共通パスを指定します。
662         *
663         * @og.tag
664         * 通常、fileQueryタグ等で、検索した結果は、PARENT,NAME というカラムにセットされます。
665         * この、fileQueryのfrom属性に、検索を開始するディレクトリを指定し、multi="true"で、
666         * 多段階展開 した場合、この、from属性に指定したパスを、inPath にセットすることで、
667         * 以下の階層フォルダそのままに、targetDir にCOPY または、MOVE することが出来ます。
668         * 逆に、指定しない場合は、フォルダ階層無しで、COPY,MOVE されます。
669         *
670         * @og.rev 6.8.0.0 (2017/06/02) 入力共通パスを指定します。
671         *
672         * @param       path    入力共通パス
673         */
674        public void setInPath( final String path ) {
675                inPath = nval( getRequestParameter( path ),inPath );
676        }
677
678        /**
679         * DBTableModel から、FromFile,ToFile を作成するための処理をまとめた補助クラスです。
680         *
681         * ここでは、オリジナルファイルやターゲットファイルを作成するための処理のみを集めています。
682         * メソッドにすると、ローカル変数を多く管理するか、多数の引数渡しを繰り返すことになるため、
683         * このローカルクラスに処理と、値を格納します。
684         *
685         */
686        private static final class FromToFiles {
687                private final DBTableModel      table ;
688
689                private final int PARENT        ;
690                private final int NAME          ;
691                private final int TO_PARENT     ;
692                private final int TO_NAME       ;
693
694                private final File              toDir   ;
695                private final boolean   createDir;      // ターゲットとなるフォルダがなければ、作成するかどうか(true:作成する)
696                private final int       inPathCnt       ;       // 6.8.0.0 (2017/06/02) 入力共通パスの文字数
697
698                private boolean equalParent     ;               // 最後に実行された処理で、親フォルダが同一の場合は、true
699
700                /**
701                 *  引数指定のコンストラクター
702                 *
703                 * 必要なパラメータを渡して、オブジェクトを構築します。
704                 * 入力共通パス(inPath)は、COPY/MOVE時にtargetDirと置換されます。
705                 *
706                 * @og.rev 6.8.0.0 (2017/06/02) 入力共通パス(inPath)を指定します。
707                 *
708                 * @param       table     一覧が格納されているDBTableModel
709                 * @param       targetDir       ターゲットとなるフォルダ
710                 * @param       createDir       フォルダ作成可否 [true:作成する/false:作成しない]
711                 * @param       inPath          入力共通パス
712                 */
713                public FromToFiles( final DBTableModel table , final String targetDir , final boolean createDir , final String inPath ) {
714                        this.table              = table;
715                        this.createDir  = createDir ;
716                        toDir = mkDirs( targetDir,createDir );                                          // targetDir が指定されていない場合は、null
717                        inPathCnt               = inPath == null ? 0 : inPath.length() ;        // 6.8.0.0 (2017/06/02) 入力共通パスの文字数
718
719                        // "PARENT","NAME","TO_PARENT","TO_NAME" のカラム名のDBTableModelのカラム番号。存在しない場合は、-1
720                        PARENT          = table.getColumnNo( "PARENT"   , false );
721                        NAME            = table.getColumnNo( "NAME"             , false );
722                        TO_PARENT       = table.getColumnNo( "TO_PARENT", false );
723                        TO_NAME         = table.getColumnNo( "TO_NAME"  , false );
724                }
725
726                /**
727                 * 行番号より、対応するオリジナルファイル(FromFile)を返します。
728                 *
729                 * ここでは、TO_PARENT や TO_NAME は、判定する必要がないため、makeFromToFile( int ) の
730                 * 一部のみで処理が終了できます。
731                 * 1.FromDir は、PARENT 列の値から作成する。
732                 * 2.FromFileは、FromDir + NAME列の値から作成する。
733                 *
734                 * 配列返しの関係で、メソッド(および内部処理)を分けています。
735                 *
736                 * @param       rowNo   カラムNo
737                 * @return      オリジナルファイル(FromFile)
738                 * @og.rtnNotNull
739                 * @see         #makeFromToFile( int )
740                 */
741                public File makeFromOnly( final int rowNo ) {
742                        final String[] value = table.getValues( rowNo );
743                        final File fromDir  = mkDirs( value[PARENT],createDir );
744
745                        return new File( fromDir, value[NAME] ) ;
746                }
747
748                /**
749                 * 行番号より、対応するオリジナルファイル(FromFile)とターゲットファイル(ToFile)を配列に格納して返します。
750                 *
751                 * ここでは、TO_PARENT や TO_NAME は、存在するかどうか不明なので、以下の手順で作成します。
752                 * 1.FromDir は、PARENT 列の値から作成する。
753                 * 2.FromFileは、FromDir + NAME列の値から作成する。
754                 * 3.toDir は、
755                 *       A.targetDir が有れば、それを使う。
756                 *       B.なければ、TO_PARENT 列の値から作成する。
757                 *       C.TO_PARENT 列がないか、値が未設定の場合は、FromDir をそのまま使う。
758                 * 4.toFile は、
759                 *       A.toDir + TO_NAME 列の値から作成する。
760                 *       B.TO_NAME 列がないか、値が未設定の場合は、toDir + NAME列の値から作成する。
761                 * 返り値は、new File[] { formFile , toFile }; とする。
762                 *
763                 * @og.rev 6.8.0.0 (2017/06/02) 入力共通パス(inPath)を指定します。
764                 *
765                 * @param       rowNo   カラムNo
766                 * @return      ファイル配列(0:オリジナルファイル 1:ターゲットファイル)
767                 * @og.rtnNotNull
768                 */
769                public File[] makeFromToFile( final int rowNo ) {
770
771                        final String[] value = table.getValues( rowNo );
772                        final File fromDir  = mkDirs( value[PARENT],createDir );
773                        final File formFile = new File( fromDir, value[NAME] );
774                        File tempToDir = toDir;
775
776                        equalParent = false;    // 最後に実行された処理で、親フォルダが同一かどうかのフラグをリセットする。
777                        if( tempToDir == null ) {
778                                if( TO_PARENT >= 0 && nval( value[TO_PARENT],null ) != null ) {
779                                        tempToDir = mkDirs( value[TO_PARENT],createDir );
780                                }
781                                else {
782                                        tempToDir = fromDir;
783                                        equalParent = true;             // 最後に実行された処理で、親フォルダが同一の場合は、true
784                                }
785                        }
786                        // 6.8.0.0 (2017/06/02) toDirが指定され、かつ、入力共通パスの文字数(inPathCnt)が指定された場合。
787                        else if( inPathCnt > 0 ) {
788                                if( TO_PARENT >= 0 && nval( value[TO_PARENT],null ) != null ) {
789                                        final String prntVal = toDir.getAbsolutePath() + value[TO_PARENT].substring( inPathCnt );
790                                        tempToDir = mkDirs( prntVal,createDir );
791                                }
792                                else {
793                                        final String prntVal = toDir.getAbsolutePath() + value[PARENT].substring( inPathCnt );
794                                        tempToDir = mkDirs( prntVal,createDir );
795                                }
796                        }
797
798                        File toFile = null;
799                        if( TO_NAME >= 0 && nval(value[TO_NAME],null) != null  ) {
800                                toFile = new File( tempToDir, value[TO_NAME] );
801                        }
802                        else {
803                                toFile = new File( tempToDir, value[NAME] );
804                        }
805
806                        return new File[] { formFile , toFile };
807                }
808
809                /**
810                 * 最後に実行された処理で、親フォルダが同一かどうかを返します(同一の場合は、true)。
811                 *
812                 * makeFromToFile( int ) が処理されたときの、FromDir と toDir が同一であれば、true を、
813                 * 異なる場合は、false を返します。
814                 * ここでの結果は、厳密な同一判定ではなく、処理的に、同一かどうかを判定しています。
815                 * つまり、toDir に FromDir をセットする(3.Cのケース)場合に、true を内部変数にセットします。
816                 * この判定値は、ファイルの移動処理で、異なる親フォルダの場合は、COPY & DELETE しなければ
817                 * なりませんが、同一親フォルダの場合は、RENAME で済む という処理負荷の軽減が目的です。
818                 * よって、結果的に、PARENT と TO_PARENT が同じとか、PARENT と targetDir が同じでも
819                 * ここでのフラグは、false が返されます。
820                 *
821                 * @return      最後に実行された処理で、親フォルダが同一の場合は、true
822                 * @see         #makeFromToFile( int )
823                 */
824                public boolean lastParentEquals() {
825                        return equalParent ;
826                }
827
828        //      /**
829        //       *  カラム名配列(String[])より、対応するカラムNo配列(int[])を作成します。
830        //       *
831        //       * ここでは、TO_PARENT や TO_NAME は、存在するかどうか不明なので、
832        //       * EXCEPTION にせず、配列番号に、-1 を返すようにしています。
833        //       *
834        //       * @param       table     一覧が格納されているDBTableModel
835        //       * @param       nameArray       カラム名配列
836        //       * @return      カラムNo配列(カラム名が存在しない場合は、-1)
837        //       */
838        //      private int[] getTableColumnNo( final DBTableModel table ,final String[] nameArray ) {
839        //              int[] clmNo = new int[ nameArray.length ];
840        //              for( int i=0; i<clmNo.length; i++ ) {
841        //                      clmNo[i] = table.getColumnNo( nameArray[i] , false );   // カラム名が存在しない場合は、-1 を返す。
842        //              }
843        //              return clmNo;
844        //      }
845
846                /**
847                 * フォルダを作成します。
848                 *
849                 * フォルダが存在しない場合は、途中階層をすべて作成します。
850                 *
851                 * @param       fname   フォルダ名
852                 * @param       createDir       フォルダ作成可否 [true:作成する/false:作成しない]
853                 * @return      フォルダを表すファイルオブジェクト。引数が null の場合は、null を返します。
854                 * @throws      HybsSystemException             ファイルか、存在しない場合に、createDir=false か、mkdirs() が false の場合
855                 */
856                private File mkDirs( final String fname , final boolean createDir ) {
857                        File target = null;
858                        if( fname != null ) {
859                                target = new File( fname );
860                                if( target.exists() ) {                 // 存在する
861                                        if( target.isFile() ) {
862                                                final String errMsg = "ターゲットに、ファイル名は指定できません。" + CR
863                                                                                        + "ターゲット=[" + fname + "]"  + CR;
864                                                throw new HybsSystemException( errMsg );
865                                        }
866                                }
867                                else {                                                  // 存在しない
868                                        // 存在しないのに、作成しない
869                                        if( !createDir ) {
870                                                final String errMsg = "ターゲットが存在しません。 " + CR
871                                                                                        + "ターゲット=[" + fname + "]"  + CR;
872                                                throw new HybsSystemException( errMsg );
873                                        }
874                                        // 作成できない
875                                        if( !target.mkdirs() ) {
876                                                final String errMsg = "ターゲットを自動作成使用としましたが、作成できませんでした。" + CR
877                                                                                        + "ターゲット=[" + fname + "]"  + CR;
878                                                throw new HybsSystemException( errMsg );
879                                        }
880                                }
881                        }
882                        return target;
883                }
884        }
885
886        /**
887         * このオブジェクトの文字列表現を返します。
888         * 基本的にデバッグ目的に使用します。
889         *
890         * @return このクラスの文字列表現
891         * @og.rtnNotNull
892         */
893        @Override
894        public String toString() {
895                return ToString.title( this.getClass().getName() )
896                                .println( "VERSION"                     ,VERSION                )
897                                .println( "action"                      ,action                 )
898                                .println( "command"                     ,command                )
899                                .println( "targetDir"           ,targetDir              )
900                                .println( "createDir"           ,createDir              )
901                                .println( "tableId"                     ,tableId                )
902                                .println( "outMessage"          ,outMessage     )
903                                .println( "displayMsg"          ,displayMsg     )
904                                .println( "selectedAll"         ,selectedAll    )
905                                .println( "keepTimeStamp"       ,keepTimeStamp  )
906                                .fixForm().toString() ;
907        }
908}