/*
 * @(#)AssertionManager.java
 *
 * Copyright 2006 Sony Corporation
 */
package info.dragonlady.sso;

import info.dragonlady.sso.util.ResourceUtils;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;


import org.apache.commons.codec.DecoderException;
import org.opensaml.SAMLAssertion;
import org.opensaml.SAMLException;
import org.opensaml.SAMLIdentifierFactory;

/**
 * SAML認証アサーション管理クラス。
 * 
 * @author Hiroshi.Ebata(INFOCITY)
 * @version $Revision: 1.4 $ $Date: 2006/03/09 14:43:26 $
 */
public class AssertionManager {

	/**
	 * AssertionManagerインスタンス。
	 */
	private static final AssertionManager am = new AssertionManager();

	/**
	 * アサーション管理マップ。
	 * <ul><li>key:アサーションハンドル文字列</li>
	 * <li>value:アサーションオブジェクトの文字列表現</li><ul>
	 */
	private volatile Map<String, String> assertionMap = Collections.synchronizedMap(new HashMap<String, String>());

	/**
	 * アサーションIDとアサーションハンドルのマップ。
	 * <ul><li>key:アサーションID</li>
	 * <li>value:アサーションハンドル文字列(base64でエンコード)</li><ul>
	 */
	private volatile Map<String, String> handleMap = Collections.synchronizedMap(new HashMap<String, String>());

	private Timer timer = new Timer();

	/**
	 * コンストラクタ。
	 */
	private AssertionManager() {
	}

	/**
	 * AssertionManagerクラスのインスタンスを取得。
	 * 
	 * @return AssertionManager インスタンス
	 */
	public static final AssertionManager getInstance() {
		return am;
	}

	/**
	 * 指定ハンドルに関連付けられたアサーションを取得。
	 * 
	 * @param handle アサーションハンドル（バイト配列）
	 * @return SAMLAssertion アサーション
	 * @throws SAMLException
	 */
	public synchronized SAMLAssertion get(byte[] handle) throws SAMLException {
		String assertion = assertionMap.get(ResourceUtils.toHexString(handle));
		// assertion文字列をオブジェクトに戻す
		return toSAMLAssertion(assertion);
	}

	/**
	 * アサーションIDに関連付けられたアサーションを取得。
	 * 
	 * @param id アサーションID
	 * @return SAMLAssertion アサーション
	 * @throws DecoderException
	 */
	public synchronized SAMLAssertion get(String id) throws SAMLException, DecoderException {
		String handleStr = handleMap.get(id);
		return handleStr != null ? get(ResourceUtils.toHexByteArray(handleStr)) : null;
	}

	/**
	 * アサーションを追加。
	 * 
	 * @param assertion アサーション
	 * @return byte[] アサーションハンドル
	 */
	public synchronized byte[] add(SAMLAssertion assertion) {
		byte[] handle = null;
		while(handle == null ||
				(handle != null && assertionMap.containsKey(ResourceUtils.toHexString(handle)))) {
			handle = SAMLIdentifierFactory.getInstance().generateRandomBytes(20);
		}

		put(handle, assertion);
		return handle;
	}

	/**
	 * アサーションIDに関連付けられたアサーションを削除する。
	 * 
	 * @param id アサーションID
	 */
	public synchronized void remove(String id) {
		String handleStr = handleMap.get(id);
		if(handleStr != null) {
			assertionMap.remove(handleStr);
			handleMap.remove(id);
		}
	}

	/**
	 * アサーションハンドルに関連付けられたアサーションを削除する。
	 * 
	 * @param handle アサーションハンドル（バイト配列）
	 */
	public synchronized void remove(byte[] handle) {
		String handleStr = ResourceUtils.toHexString(handle);
		assertionMap.remove(handleStr);
		// ハンドルとIDのマッピングを削除
		List<String> id = new ArrayList<String>();
		Iterator keyItr = handleMap.keySet().iterator();
		while(keyItr.hasNext()) {
			Object key = keyItr.next();
			if(handleMap.get(key).equals(handleStr)) {
				id.add(key.toString());
			}
		}
		for(int i = 0; i < id.size(); i++) {
			handleMap.remove(id);
		}
	}

	/**
	 * アサーションハンドルをキーとするアサーションオブジェクトのマップを取得。
	 * 
	 * @return Map アサーション管理マップ
	 */
	public synchronized Map<String, String> getAssertionMap() {
		return Collections.unmodifiableMap(assertionMap);
	}

	/**
	 * アサーションIDをキーとするアサーションハンドルのマップを取得。
	 * 
	 * @return Map アサーションハンドル管理マップ
	 */
	public synchronized Map<String, String> getHandleMap() {
		return Collections.unmodifiableMap(handleMap);
	}

	/**
	 * アサーション管理マップとアサーションハンドル管理マップを設定。
	 * 
	 * @param assertionMap アサーション管理マップ
	 * @param handleMap アサーションハンドル管理マップ
	 */
	public synchronized void setMap(Map<String, String> assertionMap, Map<String, String> handleMap) {
		clear();
		this.handleMap.putAll(handleMap);
		this.assertionMap.putAll(assertionMap);
		Iterator itr = this.assertionMap.values().iterator();
		while(itr.hasNext()) {
			try {
				schedule(toSAMLAssertion(itr.next().toString()));
			} catch(SAMLException e) {
			}
		}
	}

	/**
	 * ハンドル及びアサーションのセットを登録。
	 * 
	 * @param handle アサーションハンドル
	 * @param assertion アサーション
	 */
	public synchronized void put(byte[] handle, SAMLAssertion assertion) {
		String handleStr = ResourceUtils.toHexString(handle);
		handleMap.put(assertion.getId(), handleStr);
		// アサーションを文字列にして追加（XML）
		assertionMap.put(handleStr, assertion.toString());
		// タイマー設定
		schedule(assertion);
	}

	/**
	 * アサーション管理情報を消去する。
	 */
	public synchronized void clear() {
		this.handleMap.clear();
		this.assertionMap.clear();
	}

	/**
	 * アサーション文字列をアサーションオブジェクトに変換。
	 * 
	 * @param assertionStr
	 * @return SAMLAssertion アサーションオブジェクト
	 * @throws SAMLException
	 */
	public static final SAMLAssertion toSAMLAssertion(String assertionStr) throws SAMLException {
		return assertionStr != null ? new SAMLAssertion(new ByteArrayInputStream(assertionStr.getBytes())) : null;
	}

	private void schedule(SAMLAssertion assertion) {
		timer.schedule(new AssertionTask(assertion.getId()), assertion.getNotOnOrAfter());
	}

	/**
	 * アサーションの削除処理を行うタスク。
	 * 
	 * @author Hiroshi.Ebata(INFOCITY)
	 * @version $Revision: 1.0 $ $Date: 2007/04/24 18:34:05 $
	 */
	private class AssertionTask extends TimerTask {

		private String id = null;

		private AssertionTask(String id) {
			this.id = id;
		}

		/* (non-Javadoc)
		 * @see java.util.TimerTask#run()
		 */
		@Override
		public void run() {
			AssertionManager.getInstance().remove(id);
			cancel();
		}

	}

}
