    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.filter;
    017
    018import org.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
    019import org.opengion.hayabusa.common.HybsSystem;
    020import org.opengion.fukurou.util.StringUtil;                            // 6.3.8.0 (2015/09/11)
    021
    022import java.util.Locale;
    023
    024import java.io.File;
    025import java.io.IOException;
    026import java.io.UnsupportedEncodingException;
    027import javax.servlet.ServletRequest;
    028import javax.servlet.ServletResponse;
    029import javax.servlet.Filter;
    030import javax.servlet.FilterChain;
    031import javax.servlet.FilterConfig;
    032import javax.servlet.ServletException;
    033import javax.servlet.http.HttpServletRequest;
    034import javax.servlet.http.HttpServletResponse;
    035
    036/**
    037 * Filter インターフェースを継承した HTMLデモ画面を作成するフィルタクラスです。
    038 * web.xml で filter 設定することにより、使用できます。
    039 * このフィルターでは、通常の画面アクセスを行うと、指定のフォルダに対して
    040 * JSPをHTMLに変換した形で、ファイルをセーブしていきます。このHTMLは、
    041 * デモサンプル画面として、使用できます。
    042 * 出来る限り、デモ画面として使えるように、画面間リンクや、ボタン制御を
    043 * JavaScript を挿入する事で実現しています。
    044 *
    045 * フィルターに対してweb.xml でパラメータを設定します。
    046 *   ・saveDir   :ファイルをセーブするディレクトリ(初期値:filetemp/DIR/)
    047 *   ・omitFiles :セーブ対象外のファイルのCSV形式での指定(初期値:eventColumnMaker.jsp,realtimecheck.jsp)
    048 *
    049 * パラメータがない場合は、G:/webapps/作番/filetemp/DIR/ 以下に自動設定されます。
    050 * また、ディレクトリが、相対パスの場合は、G:/webapps/作番/ 以下に、絶対パスの
    051 * 場合は、そのパスの下に作成されます。 *
    052 *
    053 * 【WEB-INF/web.xml】
    054 *     &lt;filter&gt;
    055 *         &lt;filter-name&gt;FileFilter&lt;/filter-name&gt;
    056 *         &lt;filter-class&gt;org.opengion.hayabusa.filter.FileFilter&lt;/filter-class&gt;
    057 *         &lt;init-param&gt;
    058 *             &lt;param-name&gt;saveDir&lt;/param-name&gt;
    059 *             &lt;param-value&gt;filetemp/DIR/&lt;/param-value&gt;
    060 *         &lt;/init-param&gt;
    061 *     &lt;/filter&gt;
    062 *
    063 *     &lt;filter-mapping&gt;
    064 *         &lt;filter-name&gt;FileFilter&lt;/filter-name&gt;
    065 *         &lt;url-pattern&gt;/jsp/*&lt;/url-pattern&gt;
    066 *     &lt;/filter-mapping&gt;
    067 *
    068 * @og.group フィルター処理
    069 *
    070 * @version  4.0
    071 * @author   Kazuhiko Hasegawa
    072 * @since    JDK5.0,
    073 */
    074public class FileFilter implements Filter {
    075        private static boolean useFilter = true ;               // 6.3.8.3 (2015/10/03)
    076
    077        private String saveDir  ;                       // "G:/webapps/gf/filetemp/DIR/" など
    078        private String omitFiles = "eventColumnMaker.jsp,realtimecheck.jsp" ;   // 6.3.8.0 (2015/09/11) セーブ対象外のファイルのCSV形式での指定(初期値:)
    079
    080        /**
    081         * Filter インターフェースの doFilter メソッド
    082         *
    083         * Filter クラスの doFilter メソッドはコンテナにより呼び出され、 最後のチェーンにおける
    084         * リソースへのクライアントリクエストのために、 毎回リクエスト・レスポンスのペアが、
    085         * チェーンを通して渡されます。 このメソッドに渡される FilterChain を利用して、Filter が
    086         * リクエストやレスポンスをチェーン内の次のエンティティ(Filter)にリクエストとレスポンスを
    087         * 渡す事ができます。
    088         * このメソッドの典型的な実装は以下のようなパターンとなるでしょう。
    089         * 1. リクエストの検査
    090         * 2. オプションとして、入力フィルタリング用にコンテンツもしくはヘッダをフィルタリング
    091         *    するためにカスタム実装によるリクエストオブジェクトのラップ
    092         * 3. オプションとして、出力フィルタリング用にコンテンツもしくはヘッダをフィルタリング
    093         *    するためにカスタム実装によるレスポンスオブジェクトラップ
    094         * 4. 以下の a)、b) のどちらか
    095         *    a) FileterChain オブジェクト(chain.doFilter()) を利用してチェーンの次のエンティティを呼び出す
    096         *    b) リクエスト処理を止めるために、リクエスト・レスポンスのペアをフィルタチェーンの次の
    097         *       エンティティに渡さない
    098         * 5. フィルタチェーンの次のエンティティの呼び出した後、直接レスポンスのヘッダをセット
    099         *
    100         * @og.rev 6.3.8.3 (2015/10/03) フィルターの停止処理。
    101         * @og.rev 6.8.4.2 (2017/12/25) エンコード変換対応対応のキー(fileDownloadサーブレットでエンコードをON/OFF指定に利用)
    102         *
    103         * @param       req             ServletRequestオブジェクト
    104         * @param       res             ServletResponseオブジェクト
    105         * @param       chain   FilterChainオブジェクト
    106         * @throws IOException 入出力エラーが発生したとき
    107         * @throws ServletException サーブレット関係のエラーが発生した場合、throw されます。
    108         */
    109        public void doFilter( final ServletRequest req,
    110                                                        final ServletResponse res,
    111                                                        final FilterChain chain ) throws IOException, ServletException {
    112
    113                if( req instanceof HttpServletRequest && res instanceof HttpServletResponse ) {
    114                        final HttpServletRequest  request  = (HttpServletRequest) req;
    115
    116                        try {
    117                                request.setCharacterEncoding( "UTF-8" );
    118                        }
    119                        catch( final UnsupportedEncodingException ex ) {
    120                                throw new OgRuntimeException( ex );
    121                        }
    122
    123                        final String filename = makeFileName( request );
    124                        if( useFilter && filename != null ) {           // 6.3.8.3 (2015/10/03) フィルターの停止処理
    125                                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
    126                                final HttpServletResponse response = (HttpServletResponse) res;
    127                                final FileResponseWrapper wrappedResponse = new FileResponseWrapper(response,filename);
    128                                chain.doFilter(req, wrappedResponse);
    129                                wrappedResponse.finishResponse();
    130                        }
    131                        else {
    132                                // 6.8.4.2 (2017/12/25)
    133                                req.setAttribute( HybsSystem.USE_STR_CONV_KEY , "false" );              // FileDownloadサーブレットのファイル名文字化け対応
    134
    135                                chain.doFilter(req, res);
    136                        }
    137                }
    138        }
    139
    140        /**
    141         * フィルターの初期処理メソッドです。
    142         *
    143         * フィルターに対してweb.xml で初期パラメータを設定します。
    144         *   ・saveDir   :ファイルをセーブするディレクトリ(初期値:filetemp/DIR/)
    145         *   ・omitFiles :セーブ対象外のファイルのCSV形式での指定(初期値:eventColumnMaker.jsp,realtimecheck.jsp)
    146         *                ファイル名には、jsp まで含めてください。omitFiles.contains( jspID ) で判定します。
    147         *
    148         * @og.rev 5.7.3.2 (2014/02/28) Tomcat8 対応。getRealPath( "/" ) の互換性のための修正。
    149         * @og.rev 6.2.4.1 (2015/05/22) REAL_PATH 対応。realPath は、HybsSystem経由で、取得する。
    150         * @og.rev 6.3.8.0 (2015/09/11) セーブ対象外のファイルのCSV形式での指定(omitFiles属性)。
    151         *
    152         * @param filterConfig FilterConfigオブジェクト
    153         */
    154        public void init(final FilterConfig filterConfig) {
    155                final String realPath = HybsSystem.getRealPath();                                       // 6.2.4.1 (2015/05/22) REAL_PATH 対応
    156
    157                String dir = filterConfig.getInitParameter("saveDir");
    158                if( dir != null && dir.length() > 1 ) {
    159                        dir = dir.replace( '\\','/' );
    160                        if( dir.charAt(0) == '/' || dir.charAt(1) == ':' ) {
    161                                saveDir = dir;
    162                        }
    163                        else {
    164                                saveDir = realPath + dir ;
    165                        }
    166
    167                        if( dir.charAt(dir.length()-1) != '/' ) {
    168                                saveDir = saveDir + "/" ;
    169                        }
    170                }
    171                else {
    172                        saveDir = realPath + "filetemp/DIR/" ;
    173                }
    174
    175                // 6.3.8.0 (2015/09/11) セーブ対象外のファイルのCSV形式での指定(omitFiles属性)。
    176                omitFiles = StringUtil.nval( filterConfig.getInitParameter( "omitFiles" ) , omitFiles );
    177        }
    178
    179        /**
    180         * Filter インターフェースの destroy メソッド (何もしません)。
    181         *
    182         * サービス状態を終えた事を Filter に伝えるために Web コンテナが呼び出します。
    183         * Filter の doFilter メソッドが終了したか、タイムアウトに達した全てのスレッドにおいて、
    184         * このメソッドを一度だけ呼び出されます。 Web コンテナがこのメソッドを呼び出した後は、
    185         * Filter のこのインスタンスにおいて二度と doFilter メソッドを呼び出す事はありません。
    186         *
    187         * このメソッドは、フィルタに保持されている(例えば、メモリ、ファイルハンドル、スレッド)
    188         * 様々なリソースを開放する機会を与え、 あらゆる永続性の状態が、メモリ上における Filter
    189         * の現在の状態と同期しているように注意してください。
    190         */
    191        public void destroy() {
    192                // noop
    193        }
    194
    195        /**
    196         * フィルターの実行/停止を設定するメソッドです。
    197         *
    198         * 初期値は、true:実行 です。
    199         *
    200         * @og.rev 6.3.8.3 (2015/10/03) フィルターの停止処理。メソッド名変更、引数の意味反転。
    201         *
    202         * @param flag (true:実行  false:停止)
    203         */
    204        public static void setUseFilter( final boolean flag ) {
    205                useFilter = flag;
    206        }
    207
    208        /**
    209         * フィルターの内部状態(強制停止/解除)を取得するメソッドです。
    210         * これは、現在、アクセス制限がどうなっているかという状態ではなく、
    211         * 強制停止されているかどうかの確認メソッドです。
    212         *
    213         * @og.rev 6.3.8.3 (2015/10/03) フィルターの停止処理。メソッド名変更、戻り値の意味反転。
    214         *
    215         * @return      (true:実行  false:停止)
    216         */
    217        public static boolean isUseFilter() {
    218                return useFilter;
    219        }
    220
    221        /**
    222         * セーブするファイル名を、リクエスト情報より取得します。
    223         *
    224         * リクエストされたファイル(.jsp)を、HTMLファイル(.htm)にするだけでなく、
    225         * 呼び出されたときの command を元に、ファイル名を作成します。
    226         *   command="NEW"    + forward.jsp  ⇒  "forward.htm"
    227         *   command="RENEW"  + forward.jsp  ⇒  "renew.htm"
    228         *   command="日本語名+ forward.jsp  ⇒  "コマンド名.htm"
    229         *   command="日本語名+ update.jsp   ⇒  "コマンド名.htm"
    230         *   command="NEW"    + index.jsp    ⇒  "indexNW.htm"
    231         *   command="RENEW"  + index.jsp    ⇒  "indexRNW.htm"
    232         *   command="NEW"    + query.jsp    ⇒  "queryNW.htm"
    233         *   command="NEW"    + resultXX.jsp ⇒  "forwardXX.htm"                 5.6.3.4 (2013/04/26) result.jsp にフレームを使うパターン(３ペイン)
    234         *   matrixMenu対応
    235         *         URI分離          URI分離           request取出
    236         *      ① gamenId="jsp"  + index.jsp       + GAMENID=XXXX  ⇒ saveDir + "jsp/indexXXXX.htm"         Matrixメニューからの画面呼出し。
    237         *      ② gamenId="jsp"  + result.jsp      + GAMENID=XXXX  ⇒ saveDir + "jsp/indexXXXX.htm"         画面QUERYのヘッダーメニュー
    238         *      ③ gamenId="menu" + multiMenu.jsp   + group=YYYY    ⇒ saveDir + "menu/menuYYYY.htm"         通常メニューのグループ選択
    239         *      ④ gamenId="menu" + matrixMenu.jsp  + group=YYYY    ⇒ saveDir + "menu/matrixMenuYYYY.htm"   Matrixメニューのグループ選択
    240         *   その他             xxxx.jsp     ⇒  "xxxx.htm"
    241         *
    242         * このメソッドは、フィルタに保持されている(例えば、メモリ、ファイルハンドル、スレッド)
    243         * 様々なリソースを開放する機会を与え、 あらゆる永続性の状態が、メモリ上における Filter
    244         * の現在の状態と同期しているように注意してください。
    245         *
    246         * @og.rev 4.0.0.0 (2007/11/28) メソッドの戻り値をチェックします。
    247         * @og.rev 4.3.3.0 (2008/10/01) Matrixメニュー対応
    248         * @og.rev 5.5.2.5 (2012/05/21) update.jsp に出力されるファイルを、コマンド名.htm に出力するように機能追加
    249         * @og.rev 5.6.3.4 (2013/04/26) 5.6.3.4 (2013/04/26) command="NEW" + resultXX.jsp ⇒  "forwardXX.htm"。 result.jsp にフレームを使うパターン(３ペイン)
    250         * @og.rev 5.6.4.2 (2013/05/17) Matrixメニュー buttonRequest 廃止対応
    251         * @og.rev 6.1.0.0 (2014/12/26) refactoring: 引数を、ServletRequest から、HttpServletRequest に変更。
    252         * @og.rev 6.3.8.0 (2015/09/11) セーブ対象外のファイルのCSV形式での指定(omitFiles属性)。
    253         * @og.rev 6.3.8.4 (2015/10/09) セーブフォルダを、URIではなく、画面IDから取得する。
    254         * @og.rev 6.9.9.2 (2018/09/18) oota tmp add start 画面別にreportView.htmを出力する // oota tmp 02_「SAL1021_出荷_帳票リンク_新沼」対応
    255         *
    256         * @param request HttpServletRequestオブジェクト
    257         *
    258         * @return      セーブするファイル名
    259         */
    260        private String makeFileName( final HttpServletRequest request ) {
    261                final String requestURI = request.getRequestURI();
    262
    263                final int index2        = requestURI.lastIndexOf( '/' );
    264//              final String jspID      = requestURI.substring( index2+1 );
    265                // 6.9.9.2 (2018/09/18) oota tmp add start URLの終わりが「/」の場合は、index.jspとして判定する。
    266                final String jspID      = StringUtil.nval( requestURI.substring( index2+1 ) , "index.jsp" );
    267                final int index1        = requestURI.lastIndexOf( '/',index2-1 );
    268                String gamenId          = requestURI.substring( index1+1,index2 );
    269
    270                // 6.9.9.2 (2018/09/18) oota test add start jsの出力(commonディレクトリ以外のjsを出力する)
    271                if( jspID != null && jspID.endsWith(".js") && requestURI.indexOf("/common/") < 0 ) {
    272                        return saveDir + gamenId + "/" + jspID;                 // return file;
    273                }
    274
    275                String file = null;
    276
    277                if( jspID != null && jspID.endsWith( ".jsp" ) ) {
    278                        if( omitFiles.contains( jspID ) ) { return file; }              // 6.3.8.0 (2015/09/11) return null;
    279
    280                        final String cmd = request.getParameter( "command" );
    281                        if( cmd != null && jspID.equals( "forward.jsp" ) ) {
    282                                if( "NEW".equals( cmd ) ) { file = "forward.htm"; }
    283                                else if( "RENEW".equals( cmd ) || "REVIEW".equals( cmd ) ) { file = "renew.htm"; }
    284                                else {
    285                                        final String xferVal = request.getParameter( HybsSystem.NO_XFER_KEY + cmd );
    286                                        // 5.5.2.5 (2012/05/21) update.jsp に出力されるファイルを、コマンド名.htm に出力するように機能追加
    287                                        if( "update.jsp".equals( xferVal ) ) {
    288                                                file = cmd + ".htm" ;
    289                                        }
    290                                        else if( xferVal != null && xferVal.endsWith( "jsp" ) ) {
    291                                                file = xferVal.toLowerCase(Locale.JAPAN).replace( "jsp","htm" );
    292                                        }
    293                                        else {
    294                                                final String xferCmd = request.getParameter( HybsSystem.NO_XFER_KEY + cmd + "CMD" );
    295                                                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
    296                                                file = xferCmd == null
    297                                                                        ? cmd.toLowerCase(Locale.JAPAN) + ".htm"
    298                                                                        : xferCmd.toLowerCase(Locale.JAPAN) + ".htm";
    299
    300                                        }
    301                                }
    302                        }
    303                        else if( "index.jsp".equals( jspID ) && ( "RENEW".equals( cmd ) || "REVIEW".equals( cmd ) ) ) {
    304                                file = "indexRNW.htm";
    305                        }
    306                        else if( "index.jsp".equals( jspID ) && "NEW".equals( cmd ) ) {
    307                                file = "indexNW.htm";
    308                        }
    309                        else if( "query.jsp".equals( jspID ) && "NEW".equals( cmd ) ) {
    310                                file = "queryNW.htm";
    311                        }
    312                        // 5.6.3.4 (2013/04/26) command="NEW" + resultXX.jsp ⇒  "forwardXX.htm"。 result.jsp にフレームを使うパターン(３ペイン)
    313                        else if( jspID.startsWith( "result" ) && "NEW".equals( cmd ) ) {
    314                                file = "forward" + jspID.substring( 6,jspID.length()-4 ) + ".htm" ;
    315                        }
    316                        // 5.6.4.2 (2013/05/17) fileDownload.jsp の対応
    317                        else if( "fileDownload.jsp".equals( jspID ) ) {
    318                                gamenId = request.getParameter( "GAMENID" );    // gamenId(元はフォルダを抽出)をリクエスト変数から取得する。
    319                                // 6.4.2.1 (2016/02/05) PMD refactoring.
    320                                // 日本語ファイル名で抽出する場合。ただし、セーブ時は、UnicodeLittle なので、"fileDownload:" でマーカーする。
    321                                file = "fileDownload:" + request.getParameter( "filename" );
    322                        }
    323                        else {
    324                                file = jspID.substring( 0,jspID.length()-4 ) + ".htm" ;
    325                        }
    326
    327                        // 6.9.9.2 (2018/09/18) oota tmp add start 画面別にreportView.htmを出力する // oota tmp 02_「SAL1021_出荷_帳票リンク_新沼」対応
    328                        if( "reportView.jsp".equals( jspID ) ) {
    329                                gamenId = request.getParameter( "PGPSET" ); // reportView.jspには「PGPSET」に画面IDを渡しているので、取得できるはず。
    330                        }
    331
    332                        // 5.6.4.2 (2013/05/17) Matrixメニュー 対応
    333                        //    URI分離          URI分離           request取出
    334                        // ① gamenId="jsp"  + index.jsp       + GAMENID=XXXX  ⇒ saveDir + "jsp/indexXXXX.htm"         Matrixメニューからの画面呼出し。
    335                        // ② gamenId="jsp"  + result.jsp      + GAMENID=XXXX  ⇒ saveDir + "jsp/indexXXXX.htm"         画面QUERYのヘッダーメニュー
    336                        // ③ gamenId="menu" + multiMenu.jsp   + group=YYYY    ⇒ saveDir + "menu/menuYYYY.htm"         通常メニューのグループ選択
    337                        // ④ gamenId="menu" + matrixMenu.jsp  + group=YYYY    ⇒ saveDir + "menu/matrixMenuYYYY.htm"   Matrixメニューのグループ選択
    338                        final String guiKey = request.getParameter( "GAMENID" );
    339                        final String group  = request.getParameter( "group" );
    340
    341                        if( "jsp".equals( gamenId ) && guiKey != null ) {
    342                                if( "index.jsp".equals( jspID ) || "result.jsp".equals( jspID ) ) {
    343                                        file = "jsp/index" + guiKey + ".htm";                           // ①,②
    344                                }
    345                        }
    346                        else if( group != null ) {
    347                                if( "multiMenu.jsp".equals( jspID ) ) {
    348                                        file = gamenId + "/menu" + group + ".htm";                      // ③
    349                                }
    350                                else if( "matrixMenu.jsp".equals( jspID ) ) {
    351                                        file = gamenId + "/matrixMenu" + group + ".htm";        // ④
    352                                }
    353                                gamenId = "jsp" ;                       // トリッキー
    354                        }
    355
    356                        if( "jsp".equals( gamenId ) ) { file = saveDir + file; }
    357                        // 6.3.8.4 (2015/10/09) セーブフォルダを、URIではなく、画面IDから取得する。
    358                        // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
    359                        else if( guiKey == null ) {             file = saveDir + gamenId + "/" + file; }
    360                        else {                                                  file = saveDir + guiKey  + "/" + file; }
    361
    362                        final File fl = new File( file ).getParentFile();
    363                        if( fl != null && !fl.exists() && !fl.mkdirs() ) {
    364                                final String errMsg = "所定のフォルダが作成できませんでした。[" + fl + "]" ;
    365                                throw new OgRuntimeException( errMsg );
    366                        }
    367                }
    368                return file;
    369        }
    370}