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