/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.hayabusa.filter;

import org.opengion.fukurou.system.OgRuntimeException ;		// 6.4.2.0 (2016/01/29)
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.fukurou.util.StringUtil;				// 6.3.8.0 (2015/09/11)

import java.util.Locale;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

/**
 * Filter インターフェースを継承した HTMLデモ画面を作成するフィルタクラスです。
 * web.xml で filter 設定することにより、使用できます。
 * このフィルターでは、通常の画面アクセスを行うと、指定のフォルダに対して
 * JSPをHTMLに変換した形で、ファイルをセーブしていきます。このHTMLは、
 * デモサンプル画面として、使用できます。
 * 出来る限り、デモ画面として使えるように、画面間リンクや、ボタン制御を
 * JavaScript を挿入する事で実現しています。
 *
 * フィルターに対してweb.xml でパラメータを設定します。
 *   ・saveDir   :ファイルをセーブするディレクトリ(初期値:filetemp/DIR/)
 *   ・omitFiles :セーブ対象外のファイルのCSV形式での指定(初期値:eventColumnMaker.jsp,realtimecheck.jsp)
 *
 * パラメータがない場合は、G:/webapps/作番/filetemp/DIR/ 以下に自動設定されます。
 * また、ディレクトリが、相対パスの場合は、G:/webapps/作番/ 以下に、絶対パスの
 * 場合は、そのパスの下に作成されます。 *
 *
 * 【WEB-INF/web.xml】
 *     &lt;filter&gt;
 *         &lt;filter-name&gt;FileFilter&lt;/filter-name&gt;
 *         &lt;filter-class&gt;org.opengion.hayabusa.filter.FileFilter&lt;/filter-class&gt;
 *         &lt;init-param&gt;
 *             &lt;param-name&gt;saveDir&lt;/param-name&gt;
 *             &lt;param-value&gt;filetemp/DIR/&lt;/param-value&gt;
 *         &lt;/init-param&gt;
 *     &lt;/filter&gt;
 *
 *     &lt;filter-mapping&gt;
 *         &lt;filter-name&gt;FileFilter&lt;/filter-name&gt;
 *         &lt;url-pattern&gt;/jsp/*&lt;/url-pattern&gt;
 *     &lt;/filter-mapping&gt;
 *
 * @og.group フィルター処理
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class FileFilter implements Filter {
	private static boolean useFilter = true ;		// 6.3.8.3 (2015/10/03)

	private String saveDir	;			// "G:/webapps/gf/filetemp/DIR/" など
	private String omitFiles = "eventColumnMaker.jsp,realtimecheck.jsp" ;	// 6.3.8.0 (2015/09/11) セーブ対象外のファイルのCSV形式での指定(初期値:)

	/**
	 * Filter インターフェースの doFilter メソッド
	 *
	 * Filter クラスの doFilter メソッドはコンテナにより呼び出され、 最後のチェーンにおける
	 * リソースへのクライアントリクエストのために、 毎回リクエスト・レスポンスのペアが、
	 * チェーンを通して渡されます。 このメソッドに渡される FilterChain を利用して、Filter が
	 * リクエストやレスポンスをチェーン内の次のエンティティ(Filter)にリクエストとレスポンスを
	 * 渡す事ができます。
	 * このメソッドの典型的な実装は以下のようなパターンとなるでしょう。
	 * 1. リクエストの検査
	 * 2. オプションとして、入力フィルタリング用にコンテンツもしくはヘッダをフィルタリング
	 *    するためにカスタム実装によるリクエストオブジェクトのラップ
	 * 3. オプションとして、出力フィルタリング用にコンテンツもしくはヘッダをフィルタリング
	 *    するためにカスタム実装によるレスポンスオブジェクトラップ
	 * 4. 以下の a)、b) のどちらか
	 *    a) FileterChain オブジェクト(chain.doFilter()) を利用してチェーンの次のエンティティを呼び出す
	 *    b) リクエスト処理を止めるために、リクエスト・レスポンスのペアをフィルタチェーンの次の
	 *       エンティティに渡さない
	 * 5. フィルタチェーンの次のエンティティの呼び出した後、直接レスポンスのヘッダをセット
	 *
	 * @og.rev 6.3.8.3 (2015/10/03) フィルターの停止処理。
	 * @og.rev 6.8.4.2 (2017/12/25) エンコード変換対応対応のキー(fileDownloadサーブレットでエンコードをON/OFF指定に利用)
	 * @og.rev 8.0.0.1 (2021/10/08) USE_STR_CONV_KEY 廃止
	 *
	 * @param	req		ServletRequestオブジェクト
	 * @param	res		ServletResponseオブジェクト
	 * @param	chain	FilterChainオブジェクト
	 * @throws IOException 入出力エラーが発生したとき
	 * @throws ServletException サーブレット関係のエラーが発生した場合、throw されます。
	 */
	@Override	// Filter
	public void doFilter( final ServletRequest req,
							final ServletResponse res,
							final FilterChain chain ) throws IOException, ServletException {

		if( req instanceof HttpServletRequest && res instanceof HttpServletResponse ) {
			final HttpServletRequest  request  = (HttpServletRequest) req;

			try {
				request.setCharacterEncoding( "UTF-8" );
			}
			catch( final UnsupportedEncodingException ex ) {
				throw new OgRuntimeException( ex );
			}

			final String filename = makeFileName( request );
			if( useFilter && filename != null ) {		// 6.3.8.3 (2015/10/03) フィルターの停止処理
				// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
				final HttpServletResponse response = (HttpServletResponse) res;
				final FileResponseWrapper wrappedResponse = new FileResponseWrapper(response,filename);
				chain.doFilter(req, wrappedResponse);
				wrappedResponse.finishResponse();
			}
			else {
//				// 6.8.4.2 (2017/12/25)
//				// 8.0.0.1 (2021/10/08) cloud対応 … USE_STR_CONV_KEY 廃止(初期値を、false と同等に変更)
//				req.setAttribute( HybsSystem.USE_STR_CONV_KEY , "false" );		// FileDownloadサーブレットのファイル名文字化け対応

				chain.doFilter(req, res);
			}
		}
	}

	/**
	 * フィルターの初期処理メソッドです。
	 *
	 * フィルターに対してweb.xml で初期パラメータを設定します。
	 *   ・saveDir   :ファイルをセーブするディレクトリ(初期値:filetemp/DIR/)
	 *   ・omitFiles :セーブ対象外のファイルのCSV形式での指定(初期値:eventColumnMaker.jsp,realtimecheck.jsp)
	 *                ファイル名には、jsp まで含めてください。omitFiles.contains( jspID ) で判定します。
	 *
	 * @og.rev 5.7.3.2 (2014/02/28) Tomcat8 対応。getRealPath( "/" ) の互換性のための修正。
	 * @og.rev 6.2.4.1 (2015/05/22) REAL_PATH 対応。realPath は、HybsSystem経由で、取得する。
	 * @og.rev 6.3.8.0 (2015/09/11) セーブ対象外のファイルのCSV形式での指定(omitFiles属性)。
	 *
	 * @param filterConfig FilterConfigオブジェクト
	 */
	@Override	// Filter
	public void init(final FilterConfig filterConfig) {
		final String realPath = HybsSystem.getRealPath();					// 6.2.4.1 (2015/05/22) REAL_PATH 対応

		String dir = filterConfig.getInitParameter("saveDir");
		if( dir != null && dir.length() > 1 ) {
			dir = dir.replace( '\\','/' );
			if( dir.charAt(0) == '/' || dir.charAt(1) == ':' ) {
				saveDir = dir;
			}
			else {
				saveDir = realPath + dir ;
			}

			if( dir.charAt(dir.length()-1) != '/' ) {
				saveDir = saveDir + "/" ;
			}
		}
		else {
			saveDir = realPath + "filetemp/DIR/" ;
		}

		// 6.3.8.0 (2015/09/11) セーブ対象外のファイルのCSV形式での指定(omitFiles属性)。
		omitFiles = StringUtil.nval( filterConfig.getInitParameter( "omitFiles" ) , omitFiles );
	}

	/**
	 * Filter インターフェースの destroy メソッド (何もしません)。
	 *
	 * サービス状態を終えた事を Filter に伝えるために Web コンテナが呼び出します。
	 * Filter の doFilter メソッドが終了したか、タイムアウトに達した全てのスレッドにおいて、
	 * このメソッドを一度だけ呼び出されます。 Web コンテナがこのメソッドを呼び出した後は、
	 * Filter のこのインスタンスにおいて二度と doFilter メソッドを呼び出す事はありません。
	 *
	 * このメソッドは、フィルタに保持されている(例えば、メモリ、ファイルハンドル、スレッド)
	 * 様々なリソースを開放する機会を与え、 あらゆる永続性の状態が、メモリ上における Filter
	 * の現在の状態と同期しているように注意してください。
	 */
	@Override	// Filter
	public void destroy() {
		// noop
	}

	/**
	 * フィルターの実行/停止を設定するメソッドです。
	 *
	 * 初期値は、true:実行 です。
	 *
	 * @og.rev 6.3.8.3 (2015/10/03) フィルターの停止処理。メソッド名変更、引数の意味反転。
	 *
	 * @param flag (true:実行  false:停止)
	 */
	public static void setUseFilter( final boolean flag ) {
		useFilter = flag;
	}

	/**
	 * フィルターの内部状態(強制停止/解除)を取得するメソッドです。
	 * これは、現在、アクセス制限がどうなっているかという状態ではなく、
	 * 強制停止されているかどうかの確認メソッドです。
	 *
	 * @og.rev 6.3.8.3 (2015/10/03) フィルターの停止処理。メソッド名変更、戻り値の意味反転。
	 *
	 * @return	(true:実行  false:停止)
	 */
	public static boolean isUseFilter() {
		return useFilter;
	}

	/**
	 * セーブするファイル名を、リクエスト情報より取得します。
	 *
	 * リクエストされたファイル(.jsp)を、HTMLファイル(.htm)にするだけでなく、
	 * 呼び出されたときの command を元に、ファイル名を作成します。
	 *   command="NEW"    + forward.jsp  ⇒  "forward.htm"
	 *   command="RENEW"  + forward.jsp  ⇒  "renew.htm"
	 *   command="日本語名+ forward.jsp  ⇒  "コマンド名.htm"
	 *   command="日本語名+ update.jsp   ⇒  "コマンド名.htm"
	 *   command="NEW"    + index.jsp    ⇒  "indexNW.htm"
	 *   command="RENEW"  + index.jsp    ⇒  "indexRNW.htm"
	 *   command="NEW"    + query.jsp    ⇒  "queryNW.htm"
	 *   command="NEW"    + resultXX.jsp ⇒  "forwardXX.htm"			5.6.3.4 (2013/04/26) result.jsp にフレームを使うパターン(３ペイン)
	 *   matrixMenu対応
	 *         URI分離          URI分離           request取出
	 *      ① gamenId="jsp"  + index.jsp       + GAMENID=XXXX  ⇒ saveDir + "jsp/indexXXXX.htm"         Matrixメニューからの画面呼出し。
	 *      ② gamenId="jsp"  + result.jsp      + GAMENID=XXXX  ⇒ saveDir + "jsp/indexXXXX.htm"         画面QUERYのヘッダーメニュー
	 *      ③ gamenId="menu" + multiMenu.jsp   + group=YYYY    ⇒ saveDir + "menu/menuYYYY.htm"         通常メニューのグループ選択
	 *      ④ gamenId="menu" + matrixMenu.jsp  + group=YYYY    ⇒ saveDir + "menu/matrixMenuYYYY.htm"   Matrixメニューのグループ選択
	 *   その他             xxxx.jsp     ⇒  "xxxx.htm"
	 *
	 * このメソッドは、フィルタに保持されている(例えば、メモリ、ファイルハンドル、スレッド)
	 * 様々なリソースを開放する機会を与え、 あらゆる永続性の状態が、メモリ上における Filter
	 * の現在の状態と同期しているように注意してください。
	 *
	 * @og.rev 4.0.0.0 (2007/11/28) メソッドの戻り値をチェックします。
	 * @og.rev 4.3.3.0 (2008/10/01) Matrixメニュー対応
	 * @og.rev 5.5.2.5 (2012/05/21) update.jsp に出力されるファイルを、コマンド名.htm に出力するように機能追加
	 * @og.rev 5.6.3.4 (2013/04/26) 5.6.3.4 (2013/04/26) command="NEW" + resultXX.jsp ⇒  "forwardXX.htm"。 result.jsp にフレームを使うパターン(３ペイン)
	 * @og.rev 5.6.4.2 (2013/05/17) Matrixメニュー buttonRequest 廃止対応
	 * @og.rev 6.1.0.0 (2014/12/26) refactoring: 引数を、ServletRequest から、HttpServletRequest に変更。
	 * @og.rev 6.3.8.0 (2015/09/11) セーブ対象外のファイルのCSV形式での指定(omitFiles属性)。
	 * @og.rev 6.3.8.4 (2015/10/09) セーブフォルダを、URIではなく、画面IDから取得する。
	 * @og.rev 6.9.9.2 (2018/09/18) oota tmp add start 画面別にreportView.htmを出力する // oota tmp 02_「SAL1021_出荷_帳票リンク_新沼」対応
	 *
	 * @param request HttpServletRequestオブジェクト
	 *
	 * @return	セーブするファイル名
	 */
	private String makeFileName( final HttpServletRequest request ) {
		final String requestURI = request.getRequestURI();

		final int index2	= requestURI.lastIndexOf( '/' );
//		final String jspID	= requestURI.substring( index2+1 );
		// 6.9.9.2 (2018/09/18) oota tmp add start URLの終わりが「/」の場合は、index.jspとして判定する。
		final String jspID	= StringUtil.nval( requestURI.substring( index2+1 ) , "index.jsp" );
		final int index1	= requestURI.lastIndexOf( '/',index2-1 );
		String gamenId		= requestURI.substring( index1+1,index2 );

		// 6.9.9.2 (2018/09/18) oota test add start jsの出力(commonディレクトリ以外のjsを出力する)
		if( jspID != null && jspID.endsWith(".js") && requestURI.indexOf("/common/") < 0 ) {
			return saveDir + gamenId + "/" + jspID;			// return file;
		}

		String file = null;

		if( jspID != null && jspID.endsWith( ".jsp" ) ) {
			if( omitFiles.contains( jspID ) ) { return file; }		// 6.3.8.0 (2015/09/11) return null;

			final String cmd = request.getParameter( "command" );
			if( cmd != null && jspID.equals( "forward.jsp" ) ) {
				if( "NEW".equals( cmd ) ) { file = "forward.htm"; }
				else if( "RENEW".equals( cmd ) || "REVIEW".equals( cmd ) ) { file = "renew.htm"; }
				else {
					final String xferVal = request.getParameter( HybsSystem.NO_XFER_KEY + cmd );
					// 5.5.2.5 (2012/05/21) update.jsp に出力されるファイルを、コマンド名.htm に出力するように機能追加
					if( "update.jsp".equals( xferVal ) ) {
						file = cmd + ".htm" ;
					}
					else if( xferVal != null && xferVal.endsWith( "jsp" ) ) {
						file = xferVal.toLowerCase(Locale.JAPAN).replace( "jsp","htm" );
					}
					else {
						final String xferCmd = request.getParameter( HybsSystem.NO_XFER_KEY + cmd + "CMD" );
						// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
						file = xferCmd == null
									? cmd.toLowerCase(Locale.JAPAN) + ".htm"
									: xferCmd.toLowerCase(Locale.JAPAN) + ".htm";
					}
				}
			}
			else if( "index.jsp".equals( jspID ) && ( "RENEW".equals( cmd ) || "REVIEW".equals( cmd ) ) ) {
				file = "indexRNW.htm";
			}
			else if( "index.jsp".equals( jspID ) && "NEW".equals( cmd ) ) {
				file = "indexNW.htm";
			}
			else if( "query.jsp".equals( jspID ) && "NEW".equals( cmd ) ) {
				file = "queryNW.htm";
			}
			// 5.6.3.4 (2013/04/26) command="NEW" + resultXX.jsp ⇒  "forwardXX.htm"。 result.jsp にフレームを使うパターン(３ペイン)
			else if( jspID.startsWith( "result" ) && "NEW".equals( cmd ) ) {
				file = "forward" + jspID.substring( 6,jspID.length()-4 ) + ".htm" ;
			}
			// 5.6.4.2 (2013/05/17) fileDownload.jsp の対応
			else if( "fileDownload.jsp".equals( jspID ) ) {
				gamenId = request.getParameter( "GAMENID" );	// gamenId(元はフォルダを抽出)をリクエスト変数から取得する。
				// 6.4.2.1 (2016/02/05) PMD refactoring.
				// 日本語ファイル名で抽出する場合。ただし、セーブ時は、UnicodeLittle なので、"fileDownload:" でマーカーする。
				file = "fileDownload:" + request.getParameter( "filename" );
			}
			else {
				file = jspID.substring( 0,jspID.length()-4 ) + ".htm" ;
			}

			// 6.9.9.2 (2018/09/18) oota tmp add start 画面別にreportView.htmを出力する // oota tmp 02_「SAL1021_出荷_帳票リンク_新沼」対応
			if( "reportView.jsp".equals( jspID ) ) {
				gamenId = request.getParameter( "PGPSET" ); // reportView.jspには「PGPSET」に画面IDを渡しているので、取得できるはず。
			}

			// 5.6.4.2 (2013/05/17) Matrixメニュー 対応
			//    URI分離          URI分離           request取出
			// ① gamenId="jsp"  + index.jsp       + GAMENID=XXXX  ⇒ saveDir + "jsp/indexXXXX.htm"         Matrixメニューからの画面呼出し。
			// ② gamenId="jsp"  + result.jsp      + GAMENID=XXXX  ⇒ saveDir + "jsp/indexXXXX.htm"         画面QUERYのヘッダーメニュー
			// ③ gamenId="menu" + multiMenu.jsp   + group=YYYY    ⇒ saveDir + "menu/menuYYYY.htm"         通常メニューのグループ選択
			// ④ gamenId="menu" + matrixMenu.jsp  + group=YYYY    ⇒ saveDir + "menu/matrixMenuYYYY.htm"   Matrixメニューのグループ選択
			final String guiKey = request.getParameter( "GAMENID" );
			final String group  = request.getParameter( "group" );

			if( "jsp".equals( gamenId ) && guiKey != null ) {
				if( "index.jsp".equals( jspID ) || "result.jsp".equals( jspID ) ) {
					file = "jsp/index" + guiKey + ".htm";				// ①,②
				}
			}
			else if( group != null ) {
				if( "multiMenu.jsp".equals( jspID ) ) {
					file = gamenId + "/menu" + group + ".htm";			// ③
				}
				else if( "matrixMenu.jsp".equals( jspID ) ) {
					file = gamenId + "/matrixMenu" + group + ".htm";	// ④
				}
				gamenId = "jsp" ;			// トリッキー
			}

			if( "jsp".equals( gamenId ) ) { file = saveDir + file; }
			// 6.3.8.4 (2015/10/09) セーブフォルダを、URIではなく、画面IDから取得する。
			// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
			else if( guiKey == null ) {		file = saveDir + gamenId + "/" + file; }
			else {							file = saveDir + guiKey  + "/" + file; }

			final File fl = new File( file ).getParentFile();
			if( fl != null && !fl.exists() && !fl.mkdirs() ) {
				final String errMsg = "所定のフォルダが作成できませんでした。[" + fl + "]" ;
				throw new OgRuntimeException( errMsg );
			}
		}
		return file;
	}
}
