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

import java.util.Locale;
import java.util.concurrent.ConcurrentMap;							// 6.4.3.3 (2016/03/04)
import java.util.concurrent.ConcurrentHashMap;						// 6.4.3.1 (2016/02/12) refactoring

import org.opengion.fukurou.db.DBUtil;
import org.opengion.fukurou.db.ApplicationInfo;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.hayabusa.common.HybsSystem;
import static org.opengion.fukurou.system.HybsConst.CR ;			// 6.1.0.0 (2014/12/26)
import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

/**
 * データロールは、データへのアクセス権限を管理するクラスです。
 *
 * データロール情報は、データロールマスタ(GEA06)で管理されます。
 *
 * あるユーザーのデータロール情報に対してひもつくデータロールマスタの
 * カラム、条件値、条件式の一覧に対して、カラム単位に条件式を構築します。
 *
 * このクラスでは、インスタンス作成時にデータロールマスタのDBを直接検索しています。
 * このため、データ変更時の変更内容を次回ログイン時から反映させるため、
 * 自身のオブジェクトキャッシュは保持していません。
 *
 * また、各条件式について、1つのカラムに対して複数の条件式が適用される場合、
 * 条件式が"="または"LIKE"だけの場合は、"OR"結合されます。
 * "!="及び"NOT LIKE"条件が1つでも含まれる場合は、"AND"結合されます。
 *
 * データロールがNULLの場合、全データへのアクセス可能となり、条件式としては、
 *  "LIKE '%'" が付加されます。
 * また、'--'の場合、全データへのアクセスが不可能となり、条件式としては、
 *  "NOT LIKE '%'" が付加されます。
 * ユーザーのデータロールが指定されているにも関わらず、データロールの検索ができない
 * 場合は、全禁止になります。
 *
 * 検索条件を取得する際に、テーブル名又は、テーブル名の別名が指定された場合、
 * 条件の取得する際には、テーブル名は無視されますが、返される条件には、テーブル名
 * は付加された状態になります。
 *
 * 例)
 *  ABC(=)		BCD(=)		⇒ (CLM = 'ABC' or CLM = 'BCD' )
 *  ABC(=)		BCD(LIKE)	⇒ (CLM = 'ABC' or CLM like 'BCD%' )
 *  ABC(=)		BCD(!=)		⇒ (CLM = 'ABC' and CLM != 'BCD' )
 *  ABC(LIKE)	BCD(LIKE)	⇒ (CLM like 'ABC%' or CLM like 'BCD%' )
 *  ABC(LIKE)	BCD(!=)		⇒ (CLM like 'ABC%' and CLM != 'BCD' )
 *  ABC(LIKE)	BCD(!=)		⇒ (CLM != 'ABC' and CLM != 'BCD' )
 *  ABC(=)		BCD(=)		⇒ (A.CLM = 'ABC' or A.CLM = 'BCD' ) ※ {&#064;SEC.A.CLM}でアクセス
 *
 * @og.rev 4.4.0.0 (2009/08/02) 新規作成
 * @og.group リソース管理
 *
 * @version  4.0
 * @author   Hiroki Nakamura
 * @since    JDK5.0,
 */
public final class DataRole {
	private static final String QUERY_GEA06_SELECT
					= "select CLM, CVALUE, VALCDTN from GEA06"
						+ " where SYSTEM_ID = ? and DROLE = ? and FGJ = '1'";

	private static final int IDX_CLM	= 0;
	private static final int IDX_CVALUE	= 1;
	private static final int IDX_VALCDTN= 2;

	private static final DataRole FULL_ACCESS_DATAROLE_OBJ = new DataRole( true ) ;
	private static final DataRole FULL_DENY_DATAROLE_OBJ = new DataRole( false ) ;

	private static final String FULL_ACCESS_CONDITION = null ;
	private static final String FULL_DENY_CONDITION = " NOT LIKE '%'" ;
	private static final String FULL_DENY_DROLES_KEY = "--";

	/** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
	private final ConcurrentMap<String,String> cdtnMap = new ConcurrentHashMap<>( BUFFER_MIDDLE );

	private final String			droles ;		// データロールズ
	private final String			systemId ;		// システムID
	private final ApplicationInfo	appInfo ;
	private final String DBID = HybsSystem.sys( "RESOURCE_DBID" );		// 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対応

	private boolean		isFullAcess		;
	private boolean		isFullDeny		;

	/**
	 * 固定データロール(全アクセス可 or 全アクセス不可)のインスタンスを生成します。
	 *
	 * @param isFull true:全アクセス可 false:全アクセス不可
	 */
	private DataRole( final boolean isFull ) {
		droles = null;
		systemId = null;
		appInfo = null;

		if( isFull )	{ isFullAcess = true; }
		else			{ isFullDeny = true; }
	}

	/**
	 * ロール文字列から、データロールマスタ(GEA05)を検索し、カラム単位の
	 * 条件式を生成します。
	 *
	 * @param droles "|"で区切られた データロール文字列
	 * @param systemId システムID
	 * @param appInfo 接続情報
	 */
	private DataRole( final String droles,final String systemId, final ApplicationInfo appInfo ) {
		this.droles = droles ;		// データロールズ
		this.systemId = systemId;	// システムID
		this.appInfo = appInfo ;

		if( appInfo != null ) {
			appInfo.setModuleInfo( "DataRole",null,"CreateInstance" );
		}

		final String[] drole = StringUtil.csv2Array( droles, '|' );
		if( drole == null || drole.length == 0 ) {
			isFullAcess = true;
			return;
		}
		else {
			makeConditionMap( drole );
		}
	}

	/**
	 * ロール文字列から、データロールマスタ(GEA05)を検索し、カラム単位の
	 * 条件式を生成します。
	 *
	 * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
	 * @og.rev 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限のチェック追加
	 *
	 * @param drole データロール文字列の配列(可変長引数)
	 */
	private void makeConditionMap( final String... drole ) {
		String clm     = null;
		String cvalue  = null;
		String valcdtn = null;

		for( int i=0; i<drole.length; i++ ) {
			final String[] args = new String[] { systemId, drole[i] };
			final String[][] vals = DBUtil.dbExecute( QUERY_GEA06_SELECT,args,appInfo, DBID );	// 5.5.5.1 (2012/08/07)
			for( int j=0; j<vals.length; j++ ) {
				clm     = vals[j][IDX_CLM];
				if( clm == null ) { continue; }		// 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限

				cvalue  = vals[j][IDX_CVALUE];
				valcdtn = vals[j][IDX_VALCDTN];

				String cdtn = cdtnMap.get( clm );
				if( cdtn == null ) { cdtn = ""; }
				else if( "=".equals( valcdtn ) || "LIKE".equalsIgnoreCase( valcdtn ) ) {
					cdtn += " or ";
				}
				else {
					cdtn += " and ";
				}
				cdtn += clm + " " + valcdtn + " '" + cvalue;
				if( valcdtn.toUpperCase( Locale.JAPAN ).indexOf( "LIKE" ) >= 0 ) {
					cdtn += "%";
				}
				cdtn += "'";

				if( cdtn.indexOf( " and " ) >= 0 ) { cdtn = cdtn.replace( " or ", " and " ); }		// 6.4.2.1 (2016/02/05) よう判らん。

				cdtnMap.put( clm, cdtn );
			}
		}
	}

	/**
	 * ロール文字列から、データロールマスタ(GEA05)を検索し、カラム単位の
	 * 条件式を生成します。
	 *
	 * @param	droles	"|"で区切られた データロール文字列
	 * @param systemId システムID
	 * @param appInfo 接続情報
	 *
	 * @return データロールオブジェクト
	 * @og.rtnNotNull
	 */
	public static DataRole newInstance( final String droles, final String systemId, final ApplicationInfo appInfo ) {
		// 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method
		return droles == null || droles.isEmpty()
					? FULL_ACCESS_DATAROLE_OBJ
					: FULL_DENY_DROLES_KEY.equals( droles )
						? FULL_DENY_DATAROLE_OBJ
						: new DataRole( droles, systemId, appInfo );

	}

	/**
	 * ロールズを返します。
	 *
	 * @return ロールズ文字列
	 */
	public String getDataRoles() { return droles; }

	/**
	 * ロールズを返します。
	 *
	 * @og.rev 4.4.0.1 (2009/08/08) テーブルIDが付加されている場合の条件を追加
	 *
	 * @param clm カラム名
	 *
	 * @return ロールズ文字列
	 */
	public String getCondition( final String clm ) {
		if( isFullAcess ) { return FULL_ACCESS_CONDITION; }
		if( isFullDeny  ) { return "(" + clm + FULL_DENY_CONDITION + ")"; }

		String rtn = null;
		if( clm.indexOf( '.' ) >= 0 ) {
			final String clmTmp = clm.substring( clm.lastIndexOf( '.' ) + 1 );
			rtn = cdtnMap.get( clmTmp );
			if( rtn == null || rtn.isEmpty() ) {
				return "(" + clm + FULL_DENY_CONDITION + ")";
			}
			return "(" + rtn.replace( clmTmp, clm ) + ")";
		}
		else {
			rtn = cdtnMap.get( clm );
			if( rtn == null || rtn.isEmpty() ) {
				return "(" + clm + FULL_DENY_CONDITION + ")";
			}
			return "(" + rtn + ")";
		}
	}

	/** オブジェクトの識別子として，詳細なユーザー情報を返します。
	 *
	 * @return  詳細な画面情報
	 * @og.rtnNotNull
	 */
	@Override
	public String toString() {
		final StringBuilder rtn = new StringBuilder( BUFFER_MIDDLE );
		rtn.append( "droles  : " ).append( droles ).append( CR );
		return rtn.toString();
	}
}
