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

import java.sql.Connection;
import java.sql.CallableStatement;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;

/**
 * <P>ApplicationInfo.java は、共通的に使用される 内部情報を格納するオブジェクトです。</P>
 *
 * 内部情報とは、接続ユーザーのクライアント情報と、実行状況のアプリケーション情報があります。
 *
 * クライアント情報とは、"i=192.168.51.81,h=null,u=C12345" 的な文字列で、
 * i=の後ろに IPアドレス、h=の後ろにホスト名、u=の後ろにユーザー名をセットして、
 * DBMS_APPLICATION_INFO.SET_CLIENT_INFO( "i=192.168.51.81,h=null,u=C12345" ) を
 * CALL します。
 *
 * アプリケーション情報とは、"o=SELECT,p=GEXXXX" 的な文字列で、o=の後ろに 操作、
 * p=の後ろにプログラムIDをセットして、
 * DBMS_APPLICATION_INFO.SET_MODULE( "GE0010","o=操作,p=プログラムID" ) を
 * CALL します。
 *
 * このPL/SQL を使用するコネクションについて実行すると、アクセスログ記録を行う為の
 * 情報として取り出すことが可能になります。
 * 確認は、V$SESSION の MODULE , ACTION , CLIENT_INFO で行います。
 *
 * このクラスは、同期化されていません。
 *
 * @og.rev 3.8.7.0 (2006/12/15) 新規追加
 *
 * @version  0.9.0	2000/10/12
 * @author	 Kazuhiko Hasegawa
 * @since	 JDK1.1,
 */
public final class ApplicationInfo {
	/** SET_CLIENT_INFO( "i=192.168.51.81,h=null,u=C12345" ) */
	public static final String CLIENT_INFO = "{ call DBMS_APPLICATION_INFO.SET_CLIENT_INFO( ? ) }" ;
	/** SET_MODULE( "GE0010","o=操作,p=プログラムID" ) */
	public static final String MODULE      = "{ call DBMS_APPLICATION_INFO.SET_MODULE( ?,? ) }" ;

	private String gamenId		= null;
	private String clientInfo	= "";
	private String moduleInfo	= "";

	/**
	 * ユーザーID,IPアドレス,ホスト名 を指定して、クライアント情報を設定します。
	 *
	 * クライアント情報とは、"i=192.168.51.81,h=null,u=C12345" 的な文字列で、
	 * i=の後ろに IPアドレス、h=の後ろにホスト名、u=の後ろにユーザー名をセット
	 * しています。
	 *
	 * @param	userId	String ユーザーID
	 * @param	ipAdrs	String IPアドレス
	 * @param	host	String ホスト名
	 */
	public void setClientInfo( final String userId,final String ipAdrs,final String host ) {
		StringBuilder buf = new StringBuilder();
		append( "i=" , ipAdrs , buf );
		append( "h=" , host   , buf );
		append( "u=" , userId , buf );

		clientInfo = buf.toString() ;
	}

	/**
	 * 画面ID,操作,プログラムIDを指定して、アプリケーションに関する情報を設定します。
	 *
	 * クライアント情報とは、"o=SELECT,p=GEXXXX" 的な文字列で、
	 * o=の後ろに 操作、p=の後ろにプログラムIDをセットしています。
	 *
	 * @param	gamenId	String 画面ID
	 * @param	ope		String オペレーション(操作)
	 * @param	prgId	String プログラムID
	 */
	public void setModuleInfo( final String gamenId,final String ope,final String prgId ) {
		this.gamenId	= gamenId	;

		StringBuilder buf = new StringBuilder();
		append( "o=" , ope   , buf );
		append( "p=" , prgId , buf );

		moduleInfo = buf.toString() ;
	}

	/**
	 * setModuleInfo で最後に設定された 画面IDを返します。
	 *
	 * なにも設定されていない初期状態は、null です。
	 *
	 * @return	String 画面ID
	 * @see #setModuleInfo( String,String,String )
	 */
	public String getGamenId() {
		return gamenId ;
	}

	/**
	 * key と val を連結した文字列を作成します。
	 * 引数の val が null の場合は、何も操作しません。
	 * buf にすでになにかが登録済みの場合は、"," を追加してから、連結します。
	 *
	 * @param	key	String
	 * @param	val	String
	 * @param	buf	StringBuilder
	 */
	private void append( final String key,final String val,final StringBuilder buf ) {
		if( val != null ) {
			if( buf.length() == 0 ) { buf.append( key ).append( val ); }
			else { buf.append( ',' ).append( key ).append( val ); }
		}
	}

	/**
	 * アクセスログ記録を行う為の DBMS_APPLICATION_INFO.SET_CLIENT_INFO と、
	 * DBMS_APPLICATION_INFO.SET_MODULE を CALL します。
	 *
	 * SET_CLIENT_INFO( "i=192.168.51.81,h=null,u=C12345" ) では、
	 * クライアント情報として、"i=192.168.51.81,h=null,u=C12345" 的な文字列で、
	 * i=の後ろに IPアドレス、h=の後ろにホスト名、u=の後ろにユーザー名をセットしています。
	 *
	 * SET_MODULE( "GE0010","o=操作,p=プログラムID" )では、
	 * アプリケーションに関する情報として、"o=操作,p=プログラムID" をセットしています。
	 * 確認は、V$SESSION の MODULE , ACTION , CLIENT_INFO で行います。
	 *
	 * @param   conn 接続先(コネクション)
	 */
	public void callAppInfo( final Connection conn ) {
		CallableStatement callStmt ;
		try {
			callStmt = conn.prepareCall( CLIENT_INFO );
			callStmt.setString( 1,clientInfo );
			callStmt.executeUpdate();
			callStmt.close();

			callStmt = conn.prepareCall( MODULE );
			callStmt.setString( 1,gamenId );
			callStmt.setString( 2,moduleInfo );
			callStmt.executeUpdate();
			callStmt.close();
		}
		catch (SQLException ex) {
			String errMsg = "履歴収集処理を実行できませんでした。"
					+ ex.getMessage() + ":" + ex.getSQLState() ;
			throw new RuntimeException( errMsg,ex );
		}
	}

	/**
	 * この接続が、PreparedStatement#getParameterMetaData() を使用するかどうかを判定します。
	 *
	 * PreparedStatement に対して、String化された 数字などを setObject( int,String ) するとき、
	 * ORACLE と SQLServer は、そのまま設定すれば、自動的に変換されます。
	 * postgreSQL では、ParameterMetaData#getParameterType(int) で、カラムタイプを取得し、
	 * setObject( int,String,int ) する必要があります。
	 * その判定に、このメソッドを使用します。
	 * この結果は、あくまで、各種データベース毎の実地調査の結果を元に、判定結果を
	 * 返すようにしています。
	 * ORACLE の場合は、使用しない(false)が返るように設定しています。
	 * SQLServer では、ORACLEと同様に、false を返します。
	 *
	 * @og.rev 4.0.0.0 (2007/09/25) 新規追加
	 * @og.rev 5.1.1.0 (2009/12/01) MySQL/PostgreSQL 対応
	 *
	 * @param   conn 接続先(コネクション)
	 * @return  boolean 使用する場合：true / その他:false
	 */
	public static boolean useParameterMetaData( final Connection conn ) {
//		return false;

		try {
			DatabaseMetaData meta = conn.getMetaData();
			String dbProductName = meta.getDatabaseProductName();

	//		if( "ORACLE".equalsIgnoreCase( dbProductName ) ) { return false; }
	//		if( "Microsoft SQL Server".equalsIgnoreCase( dbProductName ) ) { return false; }

			if( "PostgreSQL".equalsIgnoreCase( dbProductName ) ) { return true; }

			return false ;
		}
		catch (SQLException ex) {
			String errMsg = "DatabaseMetaData から、getDatabaseProductName を取得できませんでした。"
					+ ex.getMessage() + ":" + ex.getSQLState() ;
			throw new RuntimeException( errMsg,ex );
		}
	}
}
