/*
 * Paraselene
 * Copyright (c) 2009  Akira Terasaki
 * このファイルは同梱されているLicense.txtに定めた条件に同意できる場合にのみ
 * 利用可能です。
 */
package paraselene.supervisor;


import paraselene.*;
import paraselene.ajax.*;
import paraselene.tag.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.zip.*;
import javax.servlet.*;
import javax.servlet.http.*;


/**
 * サイト制御(ファイルアップロード機能なし)。
 */
public abstract class Supervisor extends HttpServlet {
	/**
	 * 不正リクエスト。
	 */
	public enum IllegalRequest {
		/**
		 * 処理済みリクエストの再呼び出し。
		 */
		REPEAT_SAME_REQUEST,
		/**
		 * 呼び出し元ページが履歴に存在しない。
		 */
		NOT_FOUND_HISTORY,
		/**
		 * セッションが存在しないかタイムアウト。
		 */
		SESSION_ERROR;

		private static final long serialVersionUID = 1L;
	}

	/**
	 * 予約済みセッション項目名。
	 */
	static final String SESSION_DATA = "paraselene$data";

	public static final String[] LINK_DEF = new String[] {
		"PARASELENE(PAGE)", "PARASELENE(HTTP)", "PARASELENE(HTTPS)"
	};
	private static final int[] LINK_LEN = new int[] {
		LINK_DEF[0].length(), LINK_DEF[1].length(), LINK_DEF[2].length()
	};
	private static final String[] LINK_PROT = new String[] {
		"", "http://", "https://"
	};

	private static HashMap<Thread,DataHolder>	data_map_th
		= new HashMap<Thread,DataHolder>();
	private static HashMap<Supervisor,DataHolder>	data_map_sv
		= new HashMap<Supervisor,DataHolder>();

	static {
		SandBox.doStart();
	}

	/**
	 * コンストラクタ。
	 */
	public Supervisor() {
		super();
	}

	private static String resolvePage( DataHolder holder, String p, int from_key, int to_key ) {
		try {
			URI	u = new URI( p );
			PageID	to = null;
			Page	page = holder.sv.getPageFactory().getPage(
				Integer.parseInt( u.getPath().substring( LINK_LEN[0] ) )
			);
			if ( page != null ) {
				to = page.getID();
				to.getPageFactory().returnPage( page );
			}
			if ( to != null ) {
				StringBuilder	path = new StringBuilder(
					TransactionSequencer.makeURI(
						( page.isCheckRepeatSameRequest() )?
							holder.data.seq: null,
						holder.myself, to, from_key, to_key, !holder.search_engine
					)
				);
				String	query = u.getQuery();
				if ( query != null ) {
					path = path.append( "?" );
					path = path.append( query );
				}
				String	fragment = u.getFragment();
				if ( fragment != null ) {
					path = path.append( "#" );
					path = path.append( fragment );
				}
				return path.toString();
			}
			return "PageID_Not_Found(" + p + ")";
		}
		catch( Exception e ) {
			e.printStackTrace();
			return e.getMessage();
		}
	}

	private static String resolveHost( DataHolder holder, String path, int def_no ) {
		try {
			int	port = Integer.parseInt( path.substring( LINK_LEN[def_no] ) );
			StringBuilder	buf = new StringBuilder( LINK_PROT[def_no] );
			buf = buf.append( holder.req.getServerName() );
			if ( port > 0 && port < 0xffff ) {
				buf = buf.append( ":" );
				buf = buf.append( port );
			}
			buf = buf.append( holder.req.getContextPath() );
			return buf.toString();
		}
		catch( Exception e ) {
			e.printStackTrace();
			return e.getMessage();
		}
	}

	private static String getTarget( Tag tag ) {
		if ( tag == null )	return null;
		String	name = tag.getName();
		String	ret = null;
		if ( "a".equals( name ) || "form".equals( name ) ) {
			ret = tag.getAttributeToString( "target" );
		}
		else if ( "frame".equals( name ) || "iframe".equals( name ) ) {
			ret = tag.getAttributeToString( "name" );
			if ( ret == null ) {
				ret = tag.getAttributeToString( "id" );
			}
		}
		return ret;
	}

	/**
	 * URIへのセッション情報付加。
	 * @param uri URI。
	 * @param enc エンコード文字コード。
	 * @param tag タグ。
	 * @return セッション情報が追加されたURI。
	 */
	public static String makeWithSessionURI( String uri, String enc, Tag tag ) 
	throws UnsupportedEncodingException {
		DataHolder	holder = null;
		synchronized( data_map_th ) {
			holder = data_map_th.get( Thread.currentThread() );
		}
		if ( holder == null )	return URIValue.encode( uri, enc );
		int from_key, to_key;
		if ( tag != null ) {
			from_key = tag.getAssignedPage().getHistoryKey();
			to_key = holder.data.hist.toInt( getTarget( tag ), from_key );
		}
		else	from_key = to_key = HistorySet.ROOT;
		String[]	part = uri.split( "/" );
		boolean	flag = false;
		String	head = null;
		for ( int i = 0; i < part.length; i++ ) {
			int	def_no;
			for ( def_no = 0; def_no < LINK_DEF.length; def_no++ ) {
				if ( part[i].length() < LINK_LEN[def_no] )	continue;
				if ( LINK_DEF[def_no].equals( part[i].substring( 0, LINK_LEN[def_no] ) ) ){
					flag = true;
					break;
				}
			}
			switch( def_no ) {
			case 0:
				part[i] = resolvePage( holder, part[i], from_key, to_key );
				break;
			case 1:
			case 2:
				head = resolveHost( holder, part[i], def_no );
				part[i] = "";
				break;
			}
		}
		if ( flag ) {
			StringBuilder	buf = new StringBuilder( part[0] );
			for ( int i = 1; i < part.length; i++ ) {
				if ( buf.length() > 0 )	buf = buf.append( "/" );
				buf = buf.append( part[i] );
			}
			uri = buf.toString();
			uri = holder.response.encodeURL( URIValue.encode( uri, enc ) );
		}
		else {
			try {
				uri = URIValue.encode( uri, enc );
				URI	test = new URI( uri );
				if ( !test.isAbsolute() ) {
					uri = holder.response.encodeURL( uri );
				}
				else if ( test.getHost().equals( holder.req.getServerName() ) ) {
					uri = holder.response.encodeURL( uri );
				}
			}
			catch( Exception e ) {
				Option.debug( e );
			}
		}
		if ( head != null ) {
			StringBuilder	buf = new StringBuilder( head );
			buf = buf.append( "/" );
			buf = buf.append( uri );
			uri = buf.toString();
		}
		return uri;
	}

	/**
	 * ページファクトリーの取得。
	 * @return ページファクトリー。
	 */
	public abstract PageFactory getPageFactory();

	/**
	 * エラー発生通知。例外が発生すると呼ばれます。
	 * 内容をログへ記録する等、任意の処理を行って下さい。
	 * @param e 発生例外。
	 */
	public abstract void onError( Throwable e );

	private Forward onErrorDebug( Throwable e, HttpServletRequest req, HttpServletResponse res, int status ) {
		if ( e instanceof Page.PageException ) {
			Page.PageException	pe = (Page.PageException)e;
			e = pe.get();
		}
		Forward fw = new Forward( status );
		e.printStackTrace();
		onError( e );
		try {
			if ( !Option.isReleaseMode() ) {
				Option.debug( e );
			}
			fw.Action( this, res );
		}
		catch( Throwable e2 ) {
			e2.printStackTrace();
			onError( e2 );
		}
		HttpSession	session = req.getSession( false );
		if ( session != null ) {
			clear_session( session );
		}
		return fw;
	}
	private Forward onErrorDebug( Throwable e, HttpServletRequest req, HttpServletResponse res ) {
		return onErrorDebug( e, req, res, 500 );
	}

	private static void clear_session( HttpSession s ) {
//Thread.dumpStack();
		s.invalidate();
	}

	/**
	 * 不正呼び出しの検出通知。
	 * @param ir 検出内容。
	 * @param req リクエスト。
	 * @param from 遷移元ページ。
	 * @param to 遷移先ページ。
	 * @return 処理の変更指示。nullなら、通常通りに動作します。
	 */
	public abstract Forward onIllegalRequest( IllegalRequest ir, RequestParameter req, Page from, Page to );

	private static final Page	null4alias = new NullPage4Alias();

	private void doAction( HttpServletRequest request, HttpServletResponse res ) {
		HttpSession	session = null;
		SessionData	data = null;
		DataHolder	holder = null;
		RequestParameter	req = null;
		synchronized( data_map_th ) {
			holder = data_map_th.get( Thread.currentThread() );
			if ( holder == null )	holder = data_map_sv.get( this );
			if ( holder != null ) {
				data = holder.data;
			}
		}
		try {
			if ( data == null ) {
				System.out.println( "SessionData null" );
				res.sendError( 500 );
				return;
			}

			boolean	session_f = false;
			holder.history_use = false;
			req = holder.request;
			session = req.getSession();
			String	uri = req.getURI().toString();
			PageFactory	fact = getPageFactory();
			PathData	path_data = new PathData();
			Forward	fw = null;
			Page	last_page = null, next_page = null;

			if ( (path_data.to = fact.getPage( uri )) == null ) {
				path_data = TransactionSequencer.parsePath( data.seq, uri, fact, data.hist );
				if ( TransactionSequencer.isMustSession( uri ) ) {
					if ( holder.search_engine ) {
						new Forward( 403 ).Action( this, res );
						clear_session( session );
						return;
					}
					if ( holder.session_null && path_data != null ) {
						fw = onIllegalRequest( IllegalRequest.SESSION_ERROR, req, path_data.from, path_data.to );
						if ( fw != null ) {
							session_f = fw.session_clear_f;
							last_page = fact.getNullPage();
							next_page = fw.getPage( null );
							if ( next_page == null ) {
								fw.Action( this, res );
								if ( session_f )	if (session != null )	clear_session( session );
								return;
							}
						}
					}
				}
			}
			else {
				path_data.from = null4alias;
			}
			if ( path_data == null ) {
				fw = new Forward( 403 );
				fw.Action( this, res );
				if (session != null )	clear_session( session );
				return;
			}
			if ( path_data.redirect != null ) {
				if ( !holder.data.write( this, path_data.redirect, holder ) ) {
					fw = new Forward( 403 );
					fw.Action( this, res );
					if (session != null )	clear_session( session );
				}
				return;
			}
			last_page = path_data.from;
			next_page = path_data.to;
			if ( last_page != null ) {
				if ( last_page instanceof Null ) {
					System.out.println( "last page is null" );
					fw = new Forward( 403 );
					fw.Action( this, res );
					if (session != null )	clear_session( session );
					return;
				}
				if ( last_page.isAllowHistoryAdd() ) {
					Page	sub = data.hist.get( (path_data.from_histry != null)? path_data.from_histry: HistorySet.ROOT ).getPage( last_page.getID() );
					if ( sub != null ) {
						fact.returnPage( last_page );
						last_page = sub;
					}
					else {
						fw = onIllegalRequest( IllegalRequest.NOT_FOUND_HISTORY, req, last_page, next_page );
						if ( fw != null ) {
							session_f = fw.session_clear_f;
							last_page = fact.getNullPage();
							next_page = fw.getPage( null );
							if ( next_page == null ) {
								fw.Action( this, res );
								if ( session_f )	if (session != null )	clear_session( session );
								return;
							}
						}
					}
				}
			}
			else {
				last_page = fact.getNullPage();
			}
			if ( next_page != null ) {
				if ( next_page instanceof Null ) {
					System.out.println( "next page is null" );
					fw = new Forward( 403 );
					fw.Action( this, res );
					if (session != null )	clear_session( session );
					return;
				}
			}
			else {
				fw = new Forward( 403 );
				fw.Action( this, res );
				if (session != null )	clear_session( session );
				return;
			}

			if ( path_data.called ) {
				fw = onIllegalRequest( IllegalRequest.REPEAT_SAME_REQUEST, req, last_page, next_page );
				if ( fw != null ) {
					session_f = fw.session_clear_f;
					last_page = fact.getNullPage();
					next_page = fw.getPage( null );
					if ( next_page == null ) {
						fw.Action( this, res );
						if ( session_f )	if (session != null )	clear_session( session );
						return;
					}
				}
			}
			if ( fw == null ) {
				int	from_key = HistorySet.ROOT;
				int	to_key = HistorySet.ROOT;
				if ( path_data.from_histry != null )	from_key = path_data.from_histry;
				if ( path_data.to_histry != null )	to_key = path_data.to_histry;
				if ( next_page != null ) {
					if ( from_key != to_key ) {
						fw = new Forward( next_page.getID(), true, false );
					}
					else {
						if ( last_page.getID() == next_page.getID() ) {
							fw = new Feedback( next_page.getID() );
						}
						else if ( Closure.isClosure( last_page, next_page, req ) ) {
							fw = new Closure( next_page.getID() );
						}
						else {
							fw = new Forward( next_page.getID(), true, false );
						}
					}
					fact.returnPage( next_page );
				}
				last_page.setRequestParameter( req );
				last_page.reflect( req );
				fw = SandBox.input( last_page, req, fw );
				last_page.setInitialized( false );
				last_page.setRequestParameter( null );
				holder.history_use = fw.history_f;
				session_f = fw.session_clear_f;
				if ( fw.getPageID() == Closure.CLOSE ) {
					fw = new Feedback( last_page.getID() );
				}
				next_page = fw.getPage( last_page );
				if ( next_page == null ) {
					fw.Action( this, res );
					if ( session_f )	if (session != null )	clear_session( session );
					return;
				}
			}
			int	out_hist_key = HistorySet.ROOT;
			if ( path_data.to_histry != null ) {
				out_hist_key = path_data.to_histry;
			}
			Page[]	out_ret = output_call( out_hist_key, last_page, next_page );
			if ( out_ret == null ) {
				System.out.println( "output_call false" );
				fw = new Forward( 500 );
				fw.Action( this, res );
				return;
			}
			write( out_ret, session_f, fw, holder );
		}
		catch( Throwable e ) {
			onErrorDebug( e, request, res );
		}
	}

	HttpSession makeNewSession( HttpSession old ) {
		DataHolder	holder = null;
		synchronized( data_map_th ) {
			holder = data_map_th.get( Thread.currentThread() );
			if ( holder == null )	holder = data_map_sv.get( this );
		}
		if ( holder == null )	return old;
		if ( old != null ) {
			clear_session( old );
		}
		HttpSession	session = holder.req.getSession( true );
		session.setAttribute( SESSION_DATA, holder.data );
		return session;
	}

	private static final String XHTML = "application/xhtml+xml";

	void write( Page[] out_ret, boolean session_f, Forward fw, DataHolder holder ) throws Exception {
		SessionData data = holder.data;
		HttpServletResponse	res = holder.response;
		Page	next_page = out_ret[0];
		RequestParameter	param = holder.request;
		HttpSession	session = param.getSession();
		try {
			session.setAttribute( SESSION_DATA, data );
		}
		catch( Exception e ) {
			session = null;
		}
		if ( session_f ) {
			if ( session != null )	clear_session( session );
			session = null;
		}
		else if ( session == null ) {
			session = holder.req.getSession( true );
			session.setAttribute( SESSION_DATA, data );
		}
		holder.myself = next_page.getID();
		if ( param.getMethod() == RequestParameter.Method.POST ) {
			data.redirect( out_ret, fw, holder ).Action( this, res );
		}
		else {
			String	mime = next_page.getContentType();
			if ( mime == null ) {
				StringBuilder	buf = null;
				if ( next_page.isXML() ) {
					RequestParameter.HeaderQuality[]	head = param.getHeaderWithQuality( "Accept" );
					if ( head != null ) {
						for ( int i = 0; i < head.length; i++ ) {
							Float	f = head[i].getQuality();
							if ( f != null ) {
								if ( f <= 0.0 )	continue;
							}
							if ( !XHTML.equalsIgnoreCase( head[i].getValue() ) )	continue;
							buf = new StringBuilder( XHTML );
							break;
						}
					}
					if ( buf == null ) {
						RequestParameter.Mobile	mob = param.judgeMobile();
						switch( mob ) {
						case AU: case DOCOMO: case J_PHONE:
							buf = new StringBuilder( XHTML );
							break;
						}
					}
				}
				if ( buf == null )	buf = new StringBuilder( "text/html" );
				buf = buf.append( "; charset=" );
				mime = buf.append( next_page.getCharset() ).toString();
			}
			res.setContentType( mime );
			HTTPDate	date = next_page.getLastModified();
			String[]	mod = param.getHeader( "If-Modified-Since" );
			if ( mod != null && date != null ) {
				try {
					if ( date.compareTo( new HTTPDate( mod[0] ) ) <= 0 ) {
						res.sendError( 304 );
						data.hist.get( next_page.getHistoryKey() ).add( next_page );
						return;
					}
				}
				catch( java.text.ParseException pe ) {}
			}
			if ( date != null ) {
				res.addHeader( "Last-Modified", date.toString() );
				res.addHeader( "Cache-Control", "public" );
			}
			else {
				//res.addHeader( "Cache-Control", "no-cache, no-store" );
			}
			if ( fw != null ) {
				int	ck_cnt = fw.out_cookie.size();
				for ( int i = 0; i < ck_cnt; i++ ) {
					res.addCookie( fw.out_cookie.get( i ) );
				}
			}
			if ( gzip( param, holder.req, res, out_ret[1] ) ) {
				data.hist.get( next_page.getHistoryKey() ).add( next_page );
				return;
			}
			if ( out_ret[1] instanceof Downloadable ) {
				Downloadable	dl = (Downloadable)out_ret[1];
				OutputStream	out = new BufferedOutputStream( res.getOutputStream() );
				BufferedInputStream		in = new BufferedInputStream( dl.getInputStream() );
				try{
					byte[]	buf = new byte[4096];
					while( true ) {
						int	size = in.read( buf );
						if ( size <= 0 )	break;
						out.write( buf, 0, size );
						if ( size < buf.length )	break;
						holder.req.getSession( false );
					}
					in.close();
					out.flush();
				}
				catch( Exception e ) {
					if ( in != null ) {
						in.close();
					}
					throw e;
				}
			}
			else {
				PrintWriter	w = res.getWriter();
				out_ret[1].write( w );
				w.flush();
			}
		}
		data.hist.get( next_page.getHistoryKey() ).add( next_page );
	}

	private boolean gzip( RequestParameter param, HttpServletRequest req, HttpServletResponse res, Page page )  throws Exception {
		if ( !page.isGZIP() )	return false;
		String[]	pcol = req.getProtocol().split( "\\/" );
		if ( pcol[pcol.length - 1].equals( "1.0" ) )	return false;

		RequestParameter.HeaderQuality[]	encode =
			param.getHeaderWithQuality( "Accept-Encoding" );
		boolean	gzip_f = false;
		if ( encode != null ) {
			for ( int i = 0; i < encode.length; i++ ) {
				if ( "gzip".equalsIgnoreCase( encode[i].getValue() ) ) {
					Float	f = encode[i].getQuality();
					float	q = f == null?	1:	f;
					if ( q > 0 ) {
						gzip_f = true;
						break;
					}
				}
			}
		}
		if ( !gzip_f )	return false;

		res.addHeader( "Content-Encoding", "gzip" );
		GZIPOutputStream	gz_out = new GZIPOutputStream( res.getOutputStream() );
		if ( page instanceof Downloadable ) {
			Downloadable	dl = (Downloadable)page;
			BufferedInputStream		in = new BufferedInputStream( dl.getInputStream() );
			try {
				byte[]	buf = new byte[4096];
				while( true ) {
					int	size = in.read( buf );
					if ( size <= 0 )	break;
					gz_out.write( buf, 0, size );
					if ( size < buf.length )	break;
					req.getSession( false );
				}
				in.close();
			}
			catch( Exception e ) {
				if ( in != null )	in.close();
				throw e;
			}
		}
		else {
			PrintWriter	w = new PrintWriter(
				new OutputStreamWriter(
					gz_out,
					page.getCharset()
				)
			);
			page.write( w );
			w.flush();
		}
		gz_out.finish();

/*
		res.addHeader( "Content-Length", Integer.toString( b_out.size() ) );
		BufferedOutputStream	out = new BufferedOutputStream( res.getOutputStream() );
		b_out.writeTo( out );
*/
		return true;
	}

	private Page[] output_call( int next_key, Page last_page, Page next_page ) throws Throwable {
		next_page.setHistoryKey( next_key );
		PageID	last_id = null;
		if ( last_page instanceof NullPage ) {
			last_page = null;
		}
		if ( last_page != null ) {
			last_id = last_page.getID();
		}
		DataHolder	holder = null;
		SessionData	data = null;
		synchronized( data_map_th ) {
			holder = data_map_th.get( Thread.currentThread() );
			if ( holder == null )	holder = data_map_sv.get( this );
			if ( holder != null ) {
				data = holder.data;
			}
		}
		if ( data == null )	return null;
		if ( holder.history_use ) {
			if ( next_page.getID() == last_id ) {
				getPageFactory().returnPage( next_page );
				next_page = last_page;
			}
			else if ( next_page.isAllowHistoryAdd() ) {
				Page	sub = data.hist.get( next_key ).getPage( next_page.getID() );
				if ( sub != null ) {
					getPageFactory().returnPage( next_page );
					next_page = sub;
				}
			}
		}
		next_page.setRequestParameter( holder.request );
		Page	view = SandBox.output( next_page, last_page, holder.request );
		if ( view == null )	view = next_page;
		next_page.setInitialized( false );
		next_page.setRequestParameter( null );
		return new Page[]{ next_page, view };
	}

	RequestParameter normalReq( HttpServletRequest req ) {
		RequestParameter	ret = new RequestParameter();
		Enumeration<?>	e = req.getParameterNames();
		while( e.hasMoreElements() ) {
			String	key = (String)e.nextElement();
			String[]	val = req.getParameterValues( key );
			for ( int i = 0; i < val.length; i++ ) {
				ret.addItem( key, val[i] );
			}
		}
		return ret;
	}

	/**
	 * 接続制限数を返す。戻り値を上回ると503を返します。
	 * @return 0以下なら無制限。1以上なら制限する。
	 */
	public abstract int getRequestMaxCount();

	private Forward preset( RequestParameter.Method method, HttpServletRequest request, HttpServletResponse res ) throws UnsupportedEncodingException {
		if ( !SandBox.isSafeFreeMemory() ) {
			return new Forward( 503 );
		}
		DataHolder	holder = new DataHolder( this );
		HttpSession	session = request.getSession( false );
		SessionData	data = null;
		if ( session == null ) {
			data = new SessionData( request );
			holder.session_null = true;
		}
		else {
			data = (SessionData)session.getAttribute( SESSION_DATA );
			holder.session_null = false;
			if ( data == null ) {
				data = new SessionData( request );
				session.setAttribute( SESSION_DATA, data );
				holder.session_null = true;
			}
			else if ( !data.isSame( request ) ) {
				clear_session( session );
				session = null;
				data = new SessionData( request );
				holder.session_null = true;
			}
		}
		holder.req = request;
		holder.response = res;
		holder.data = data;

		PathData	p = TransactionSequencer.parsePath( null, request.getRequestURI() , getPageFactory(), null );
		if ( p != null ) {
			if ( p.from != null ) {
				request.setCharacterEncoding( p.from.getCharset() );
			}
		}
		RequestParameter	req = null;
		try {
			req = parseRequest( request, p );
		}
		catch( Exception e ){
			return onErrorDebug( e, request, res, 422 );
		}
		req.setParam( method, request, this );
		holder.search_engine = req.judgeSearchEngine() != RequestParameter.SearchEngine.NO_SEARCHENGINE;
		holder.request = req;

		int	cnt = getRequestMaxCount();
		synchronized( data_map_th ) {
			if ( cnt > 0 ) {
				if ( cnt < data_map_th.size() )	return new Forward( 503 );
			}
			data_map_th.put( Thread.currentThread(), holder );
			data_map_sv.put( this, holder );
		}
		return null;
	}

	RequestParameter parseRequest( HttpServletRequest request, PathData p ) throws Exception {
		String	head = request.getHeader( "Content-Type" );
		if ( head != null ) {
			head = head.split( ";" )[0];
			if ( paraselene.tag.form.Form.MULTI_PART.equals( head ) ) {
				throw new Exception( "Gate class must extend UploadableSupervisor!" );
			}
		}
		return normalReq( request );
	}

	protected final void doGet( HttpServletRequest req, HttpServletResponse res )  {
		try {
			Forward	fw = preset( RequestParameter.Method.GET, req, res );
			if ( fw != null ) {
				fw.Action( this, res );
				return;
			}
			doAction( req, res );
			synchronized( data_map_th ) {
				data_map_th.remove( Thread.currentThread() );
				data_map_sv.remove( this );
			}
		}
		catch( Exception e ) {
			onErrorDebug( e, req, res );
		}
	}

	protected final void doPost( HttpServletRequest req, HttpServletResponse res )  {
		try {
			Forward	fw = preset( RequestParameter.Method.POST, req, res );
			if ( fw != null ) {
				fw.Action( this, res );
				return;
			}
			doAction( req, res );
			synchronized( data_map_th ) {
				data_map_th.remove( Thread.currentThread() );
				data_map_sv.remove( this );
			}
		}
		catch( Exception e ) {
			onErrorDebug( e, req, res );
		}
	}
}

