/*
 * 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.taglib;

import static org.opengion.fukurou.util.StringUtil.nval;

import java.io.File;
import java.io.IOException;

import javax.servlet.http.HttpServletRequest;

import org.opengion.fukurou.db.Transaction;
import org.opengion.fukurou.db.TransactionReal;
import org.opengion.fukurou.util.FileUtil;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.db.DBTableModel;
import org.opengion.hayabusa.html.ViewForm;
import org.opengion.hayabusa.html.ViewFormFactory;
import org.opengion.hayabusa.io.FileManager;
import org.opengion.hayabusa.io.FileManager.ACTION;
import org.opengion.hayabusa.servlet.MultipartRequest;

/**
 * クライアントのファイルをサーバーにアップロードするタグです。
 *
 * アップロード後の属性は、DBTableModel に格納することにより、通常のデータと
 * 同様の取り出し方が可能です。
 * また、通常のファイルアップロード時の、form で使用する、enctype="multipart/form-data"
 * を指定した場合の、他のリクエスト情報も、{&#064;XXXX} 変数で取り出すことが可能です。
 *
 * この upload タグでは、アップロード後に、指定のファイル名に変更する機能があります。
 * file 登録ダイアログで指定した name に、"_NEW" という名称を付けたリクエスト値を
 * ファイルのアップロードと同時に送信することで、この名前にファイルを付け替えます。
 * また、アップロード後のファイル名は、name 指定の名称で、取り出せます。
 * クライアントから登録したオリジナルのファイル名は、name に、"_ORG" という名称
 * で取り出すことが可能です。
 *
 * 新ファイル名に拡張子が設定されていない場合は、オリジナルファイル名の拡張子をセット
 * します。
 *
 * @og.formSample
 * ●形式：&lt;og:upload fileURL="…" maxPostSize="…" /&gt;
 * ●body：なし
 *
 * ●使用例 :
 *
 * 【query.jsp】
 *       &lt;form method="POST" action="result.jsp" enctype="multipart/form-data" target="RESULT"&gt;
 *       &lt;table summary="layout" &gt;
 *       &lt;tr&gt;&lt;og:input type="text" name="submitter" value="{&#064;USER.JNAME}" size="20" msg="MSG0014" /&gt;&lt;/tr&gt;
 *       &lt;tr&gt;
 *           &lt;og:input type="file" name="file1"      size="30" msg="MSG0015" /&gt;
 *           &lt;og:input             name="file1_NEW"  size="10" lbl="FILENAME" /&gt;
 *       &lt;/tr&gt;&lt;tr&gt;
 *           &lt;og:input type="file" name="file2"      size="30" msg="MSG0015" /&gt;
 *           &lt;og:input             name="file2_NEW"  size="10" lbl="FILENAME" /&gt;
 *       &lt;/tr&gt;&lt;tr&gt;
 *           &lt;og:input type="file" name="file3"      size="30" msg="MSG0015" /&gt;
 *           &lt;og:input             name="file3_NEW"  size="10" lbl="FILENAME" /&gt;
 *       &lt;/tr&gt;&lt;tr&gt;
 *           &lt;og:column name="writable"     value="false"  /&gt;
 *       &lt;/tr&gt;
 *       &lt;/table&gt;
 *
 * 【result.jsp】
 *       &lt;og:upload
 *           fileURL     = "{&#064;USER.ID}"
 *       /&gt;
 *       &lt;br /&gt;
 *       &lt;og:message msg="MSG0003" comment="ファイルの登録が完了しました。" /&gt;
 *
 *       &lt;og:view
 *           command      = "NEW"
 *           viewFormType = "HTMLTable"
 *           writable     = "{&#064;writable}"
 *       /&gt;
 *
 *       &lt;table&gt;
 *       &lt;tr&gt;&lt;og:input name="submitter" value="{&#064;submitter}" /&gt;&lt;/tr&gt;
 *       &lt;tr&gt;&lt;og:input name="writable"  value="{&#064;writable}"  /&gt;&lt;/tr&gt;
 *       &lt;tr&gt;&lt;og:input name="directory" value="{&#064;directory}" /&gt;&lt;/tr&gt;
 *       &lt;tr&gt;&lt;og:input name="file1"     value="{&#064;file1}"     /&gt;&lt;/tr&gt;
 *       &lt;tr&gt;&lt;og:input name="file1_NEW" value="{&#064;file1_NEW}" /&gt;&lt;/tr&gt;
 *       &lt;tr&gt;&lt;og:input name="file1_ORG" value="{&#064;file1_ORG}" /&gt;&lt;/tr&gt;
 *       &lt;tr&gt;&lt;og:input name="file2"     value="{&#064;file2}"     /&gt;&lt;/tr&gt;
 *       &lt;tr&gt;&lt;og:input name="file2_NEW" value="{&#064;file2_NEW}" /&gt;&lt;/tr&gt;
 *       &lt;tr&gt;&lt;og:input name="file2_ORG" value="{&#064;file2_ORG}" /&gt;&lt;/tr&gt;
 *       &lt;tr&gt;&lt;og:input name="file3"     value="{&#064;file3}"     /&gt;&lt;/tr&gt;
 *       &lt;tr&gt;&lt;og:input name="file3_NEW" value="{&#064;file3_NEW}" /&gt;&lt;/tr&gt;
 *       &lt;tr&gt;&lt;og:input name="file3_ORG" value="{&#064;file3_ORG}" /&gt;&lt;/tr&gt;
 *       &lt;/table&gt;
 *
 * @og.group ファイル入力
 *
 * @version  4.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class FileManagerTag extends CommonTagSupport {
	//* このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "4.0.0 (2005/08/31)" ;

	private static final long serialVersionUID = 4000 ;	// 4.0.0 (2005/01/31)

	private static final String	ENCODE			= "UTF-8";
	private static final char	FS				= File.separatorChar;
	private static final String SYSTEM_ID		= HybsSystem.sys( "SYSTEM_ID" );
	private static final String	FILE_BASE_URL 	= HybsSystem.sys( "FILE_URL" ) + "FileMdTemp/";


		

	private ACTION	action				= ACTION.VIEW;
	private String	syfiles				= null;
	private String	refKey				= null;
	private int		freeCount			= 3;
	private String	searchKeys			= null;
	private String	dbid				= null;
	private String	tableId 			= HybsSystem.TBL_MDL_KEY ;
	private int 	maxPostSize			= 10*1024*1024; // 最大ファイル容量  10MB

	private Transaction tran			= null;

	/**
	 * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
	 *
	 * @return  int 後続処理の指示
	 */
	@Override
	public int doStartTag() {
		TransactionTag tranTag = (TransactionTag)findAncestorWithClass( this,TransactionTag.class );
		if( tranTag == null ) {
			tran = new TransactionReal( dbid,getApplicationInfo() );
		}
		else {
			tran = tranTag.getTransaction();
		}

		return ( SKIP_BODY );
	}

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @return	int
	 */
	@Override
	public int doEndTag() {
		debugPrint();

		boolean errFlag = true;
		try {
			exec();
			tran.commit();
			errFlag = false;
		}
		catch( HybsSystemException ex ) {
			tran.rollback();
			String errMsg = "ファイル管理モジュールの処理に失敗しました。" + HybsSystem.CR + HybsSystem.CR ;
			throw new HybsSystemException( errMsg, ex );
		}
		finally {
			tran.close( errFlag );
		}

		return(EVAL_PAGE);
	}

	private void exec() {
		String tmpFileDir = null;
		MultipartRequest multi = null;
		if( action == FileManager.ACTION.UPLOAD ) {
			tmpFileDir = FILE_BASE_URL + getUser().getUserID() + FS + System.currentTimeMillis() + FS;
			tmpFileDir = HybsSystem.url2dir( tmpFileDir );
			multi = upload( tmpFileDir );

			syfiles = nval( getRequestValue( "syfiles" ),syfiles );
			refKey = nval( getRequestValue( "refKey" ),refKey );
			freeCount = nval( getRequestValue( "freeCount" ),freeCount );
			searchKeys = nval( getRequestValue( "searchKeys" ),searchKeys );
		}

		FileManager fm = new FileManager( SYSTEM_ID, syfiles, refKey, searchKeys );
		fm.setDbid( dbid );
		fm.setTransaction( tran );
		fm.setResource( getResource() );
		fm.setUser( getUser() );
		fm.init();

		if( action == FileManager.ACTION.VIEW ) {
			DBTableModel table = fm.view();
			ViewForm form = ViewFormFactory.newInstance( "HTMLTable" );
			form.init( table );
			jspPrint( form.create() );
		}
		else if( action == FileManager.ACTION.INPUT ) {
			jspPrint( "<input type=\"hidden\" name=\"refKey\" value=\"" + refKey + "\"/>" );
			jspPrint( "<input type=\"hidden\" name=\"syfiles\" value=\"" + syfiles + "\"/>" );
			jspPrint( "<input type=\"hidden\" name=\"freeCount\" value=\"" + freeCount + "\"/>" );
			jspPrint( "<input type=\"hidden\" name=\"searchKeys\" value=\"" + searchKeys + "\"/>" );

			DBTableModel table = fm.input( freeCount );
			ViewForm form = ViewFormFactory.newInstance( "HTMLTable" );
			form.init( table );
			form.setColumnWritable( "UPFILE,CONTENTS" );
			jspPrint( form.create() );
		}
		else if( action == FileManager.ACTION.UPLOAD ) {
			String[] syfileArr = StringUtil.csv2Array( syfiles );
			String[] fileNameArr = new String[syfileArr.length+freeCount];
			String[] contentArr = new String[syfileArr.length+freeCount];

			String[] formNames = multi.getFilenames();
			for( String formName : formNames ) {
				int row =  Integer.valueOf( formName.split( HybsSystem.JOINT_STRING )[1] );
				String fileName = multi.getOriginalFileName( formName );
				fileNameArr[row] = fileName;
				contentArr[row] = getRequestValue( "CONTENTS" + HybsSystem.JOINT_STRING + row );
			}

			fm.upload( tmpFileDir, fileNameArr, contentArr, getGUIInfoAttri( "KEY" )  );

			FileUtil.deleteFiles( new File( tmpFileDir ) );
		}
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 *
	 */
	@Override
	protected void release2() {
		super.release2();
		action			= FileManager.ACTION.VIEW;
		syfiles			= null;
		refKey			= null;
		freeCount		= 3;
		searchKeys		= null;
		dbid			= null;
		tableId			= HybsSystem.TBL_MDL_KEY;
		maxPostSize		= 10*1024*1024;
	}

	private MultipartRequest upload( final String directory ) {
		HttpServletRequest request = (HttpServletRequest) getRequest();

		MultipartRequest multi = null;
		try {
			File dir = new File( directory );
			if( !dir.exists() && !dir.mkdirs() ) {
				String errMsg = "ディレクトリの作成に失敗しました。[" + directory + "]";
				throw new HybsSystemException( errMsg );
			}

			multi = new MultipartRequest( request, directory, maxPostSize, ENCODE, null );
		}
		catch( IOException ex ) {
			String errMsg = "ファイルの取り扱い中にエラーが発生しました。" + toString()
							+ HybsSystem.CR + "TempFileDir=" + directory
							+ HybsSystem.CR + ex.getMessage();
			throw new HybsSystemException( errMsg, ex );
		}

		String[] params = multi.getParameterNames();
		for( String name : params ) {
			setRequestAttribute( name, multi.getParameter( name ) );
		}

		return multi;
	}




	/**
	 * 【TAG】タブの一覧をどこから取得するかを指定します(初期値:AUTO)
	 *
	 * @og.tag
	 * タブの一覧をどこから取得するかを指定します。
	 * 現状の実装では、クエリを発行して一覧を生成する「DB」と、子タグである
	 * tabListタグを列挙してタブを定義する「TAG」が実装されています。
	 * 
	 * また、「AUTO」と指定した場合は、Body部分の内容に応じて自動的に判定されます。
	 * 初期値は、「AUTO」です。
	 *
	 * @param	tp タブ一覧取得方法(「AUTO」)
	 */
	public void setAction( final String ac ) {
		String acStr = nval( getRequestParameter( ac ), null );
		try {
			action = FileManager.ACTION.valueOf( acStr );
		}
		catch ( IllegalArgumentException ex ) {
			StringBuilder errBuf = new StringBuilder( 100 );
			errBuf.append( "listType は" );
			for ( FileManager.ACTION obj : FileManager.ACTION.values() ) {
				errBuf.append( ',' );
				errBuf.append( obj.name() );
			}
			errBuf.append( "から選んでください。" );
			throw new HybsSystemException( errBuf.toString(), ex );
		}
	}

	/**
	 * 【TAG】タブの一覧をどこから取得するかを指定します(初期値:AUTO)
	 *
	 * @og.tag
	 * タブの一覧をどこから取得するかを指定します。
	 * 現状の実装では、クエリを発行して一覧を生成する「DB」と、子タグである
	 * tabListタグを列挙してタブを定義する「TAG」が実装されています。
	 * 
	 * また、「AUTO」と指定した場合は、Body部分の内容に応じて自動的に判定されます。
	 * 初期値は、「AUTO」です。
	 *
	 * @param	keys タブ一覧取得方法(「AUTO」)
	 */
	public void setSyfiles( final String keys ) {
		syfiles = nval( getRequestParameter( keys ),syfiles );
	}

	/**
	 * 【TAG】リクエスト情報の クォーティション(') 存在チェックを実施するかどうか(true/false)を設定します(初期値:USE_SQL_INJECTION_CHECK)。
	 *
	 * @og.tag
	 * ＳＱＬインジェクション対策の一つとして、暫定的ではありますが、SQLのパラメータに
	 * 渡す文字列にクォーティション(') を許さない設定にすれば、ある程度は防止できます。
	 * 数字タイプの引数には、 or 5=5 などのクォーティションを使用しないコードを埋めても、
	 * 数字チェックで検出可能です。文字タイプの場合は、必ず (')をはずして、
	 * ' or 'A' like 'A のような形式になる為、(')チェックだけでも有効です。
	 * (') が含まれていたエラーにする（true)／かノーチェックか(false)を指定します。
	 * 初期値は、SystemData#USE_SQL_INJECTION_CHECK です。
	 *
	 * @og.rev 4.0.0 (2005/08/31) 新規追加
	 *
	 * @param   key クォーティションチェックする ("true")／しない (それ以外)
	 */
	public void setRefKey( final String key ) {
		refKey = nval( getRequestParameter( key ),refKey );
	}

	/**
	 * 【TAG】タブの一覧をどこから取得するかを指定します(初期値:AUTO)
	 *
	 * @og.tag
	 * タブの一覧をどこから取得するかを指定します。
	 * 現状の実装では、クエリを発行して一覧を生成する「DB」と、子タグである
	 * tabListタグを列挙してタブを定義する「TAG」が実装されています。
	 * 
	 * また、「AUTO」と指定した場合は、Body部分の内容に応じて自動的に判定されます。
	 * 初期値は、「AUTO」です。
	 *
	 * @param	cnt タブ一覧取得方法(「AUTO」)
	 */
	public void setFreeCount( final String cnt ) {
		freeCount = nval( getRequestParameter( cnt ),freeCount );
	}



	/**
	 * 【TAG】タブの一覧をどこから取得するかを指定します(初期値:AUTO)
	 *
	 * @og.tag
	 * タブの一覧をどこから取得するかを指定します。
	 * 現状の実装では、クエリを発行して一覧を生成する「DB」と、子タグである
	 * tabListタグを列挙してタブを定義する「TAG」が実装されています。
	 * 
	 * また、「AUTO」と指定した場合は、Body部分の内容に応じて自動的に判定されます。
	 * 初期値は、「AUTO」です。
	 *
	 * @param	searchKeys タブ一覧取得方法(「AUTO」)
	 */
	public void setSearchKeys( final String keys ) {
		searchKeys = nval( getRequestParameter( keys ),searchKeys );
	}

	/**
	 * 【TAG】(通常使いません)sessionから所得する DBTableModel オブジェクトの ID。
	 *
	 * @og.tag
	 * 初期値は、HybsSystem.TBL_MDL_KEY です。
	 *
	 * @og.rev 3.5.2.0 (2003/10/20) nvalメソッドを適用するように変更。
	 *
	 * @param	id sessionに登録する時の ID
	 */
	public void setTableId( final String id ) {
		tableId = nval( getRequestParameter( id ),tableId );
	}

	/**
	 * 【TAG】(通常使いません)sessionから所得する DBTableModel オブジェクトの ID。
	 *
	 * @og.tag
	 * 初期値は、HybsSystem.TBL_MDL_KEY です。
	 *
	 * @og.rev 3.5.2.0 (2003/10/20) nvalメソッドを適用するように変更。
	 *
	 * @param	id sessionに登録する時の ID
	 */
	public void setDbid( final String id ) {
		dbid = nval( getRequestParameter( id ),dbid );
	}

	/**
	 * 【TAG】最大転送サイズ(Byte)を指定します(初期値:10485760)。
	 *
	 * @og.tag
	 * 最大転送サイズを指定します。初期値は、10*1024*1024 = 10MB です。
	 * 指定は、Byte 単位で指定します。
	 *
	 * @og.rev 3.0.1.1 (2003/03/06) maxPostSize の設定バグ修正。
	 *
	 * @param	maxPS 最大転送サイズ
	 */
	public void setMaxPostSize( final String maxPS ) {
		maxPostSize = nval( getRequestParameter( maxPS ),maxPostSize );
	}

	/**
	 * タグの名称を、返します。
	 * 自分自身のクラス名より、自動的に取り出せないため、このメソッドをオーバーライドします。
	 *
	 * @og.rev 4.0.0 (2005/01/31) 新規追加
	 *
	 * @return  タグの名称
	 */
	protected String getTagName() {
		return "fileManager" ;
	}

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @return このクラスの文字列表現
	 */
	public String toString() {
		return org.opengion.fukurou.util.ToString.title( this.getClass().getName() )
				.println( "VERSION"		,VERSION	)
				.println( "ENCODE"		,ENCODE		)
				.println( "maxPostSize" ,maxPostSize)
				.println( "tableId" 	,tableId	)
				.println( "Other..."	,getAttributes().getAttribute() )
				.fixForm().toString() ;
	}
}