/*
 * Copyright 2006 Takahiro Nakamura.
 *
 * 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 woolpack.ee;

import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;

import woolpack.dom.DomContext;
import woolpack.dom.DomExpression;
import woolpack.utils.CheckUtils;

/**
 * 別のセッションからセッションをクリアするための{@link DomExpression}のビルダ。 本クラスは Servlet API
 * のライブラリを使用する。 適用しているパターン：Hook Operation。
 * 
 * @author nakamura
 * 
 */
public class SessionClearBuilder {

	/**
	 * セッション識別子の取得する{@link DomContext#getSession()}上のキーのデフォルト値。
	 */
	public static final String SESSION_ID_KEY = "woolpack.ee.SESSION_CLEAR_ID";

	/**
	 * ログイン名の取得する{@link DomContext#getSession()}上のキーのデフォルト値。
	 */
	public static final String NAME_KEY = "woolpack.ee.SESSION_CLEAR_NAME";

	/**
	 * セッションクリアを sessionIdNameMap と clearSessionIdSet に反映させるための
	 * {@link HttpSessionBindingListener}を格納する
	 * {@link DomContext#getSession()}上のキーのデフォルト値。
	 */
	public static final String REMOVER_KEY
	= "woolpack.ee.SESSION_CLEAR_REMOVER";

	private final String sessionIdKey;

	private final String nameKey;

	private final String removerKey;

	private final Map<String, String> sessionIdNameMap;

	private final Set<String> clearSessionIdSet;

	/**
	 * コンストラクタ。
	 * 
	 * @param sessionIdKey
	 *            セッション識別子の取得する{@link DomContext#getSession()}上のキー。
	 * @param nameKey
	 *            ログイン名の取得する{@link DomContext#getSession()}上のキー。
	 * @param removerKey
	 *            セッションクリアを sessionIdNameMap と clearSessionIdSet
	 *            に反映させるための{@link HttpSessionBindingListener}
	 *            を格納する{@link DomContext#getSession()}上のキー。
	 * @param sessionIdNameMap
	 *            セッション識別子とログイン名の{@link Map}。
	 * @param clearSessionIdSet
	 *            セッションクリア対象のセッション識別子の{@link Set}。
	 * @throws NullPointerException
	 *             引数のいずれかが null の場合。
	 * @throws StringIndexOutOfBoundsException
	 *             keyKey または removerKey が空の場合。
	 */
	public SessionClearBuilder(final String sessionIdKey, final String nameKey,
			final String removerKey,
			final Map<String, String> sessionIdNameMap,
			final Set<String> clearSessionIdSet) {
		CheckUtils.checkNotEmpty(sessionIdKey);
		CheckUtils.checkNotEmpty(nameKey);
		CheckUtils.checkNotEmpty(removerKey);
		CheckUtils.checkNotNull(sessionIdNameMap);
		CheckUtils.checkNotNull(clearSessionIdSet);

		this.sessionIdKey = sessionIdKey;
		this.nameKey = nameKey;
		this.removerKey = removerKey;
		this.sessionIdNameMap = sessionIdNameMap;
		this.clearSessionIdSet = clearSessionIdSet;
	}

	/**
	 * コンストラクタ。
	 * 
	 * @param sessionIdNameMap
	 *            セッション識別子とログイン名の{@link Map}。
	 * @param clearSessionIdSet
	 *            セッションクリア対象のセッション識別子の{@link Set}。
	 * @throws NullPointerException
	 *             引数のいずれかが null の場合。
	 */
	public SessionClearBuilder(final Map<String, String> sessionIdNameMap,
			final Set<String> clearSessionIdSet) {
		this(SESSION_ID_KEY, NAME_KEY, REMOVER_KEY, sessionIdNameMap,
				clearSessionIdSet);
	}

	/**
	 * ログインされたセッションを本インスタンスに登録する{@link DomExpression}を返す。
	 * 
	 * @return ログインされたセッションを本インスタンスに登録する{@link DomExpression}。
	 */
	public DomExpression getInitExpression() {
		return new DomExpression() {
			public void interpret(final DomContext context) {
				final String sessionId = (String) context.getSession().get(
						sessionIdKey);
				final String name = (String) context.getSession().get(nameKey);
				// セッション識別子とログイン名が割り振られていてセッションが本インスタンスに登録されていない場合
				if (sessionId != null && name != null
						&& !context.getSession().containsKey(removerKey)) {
					final Object o = new HttpSessionBindingListener() {
						public void valueBound(
								final HttpSessionBindingEvent arg0) {
							// セッションを本インスタンスに登録する宣言
							sessionIdNameMap.put(sessionId, name);
						}

						public void valueUnbound(
								final HttpSessionBindingEvent arg0) {
							// セッションクリア処理の宣言
							clearSessionIdSet.remove(sessionId);
							sessionIdNameMap.remove(sessionId);
						}
					};
					// セッションが本インスタンスに登録されていないことを再検証し登録する
					context.getSession().putIfAbsent(removerKey, o);
				}
			}
		};
	}

	/**
	 * clearSessionIdSet に登録されているセッションをクリアする{@link DomExpression}を返す。
	 * 
	 * @return clearSessionIdSet に登録されているセッションをクリアする{@link DomExpression}。
	 */
	public DomExpression getClearExpression() {
		return new DomExpression() {
			public void interpret(final DomContext context) {
				final String sessionId = (String) context.getSession().get(
						sessionIdKey);
				if (clearSessionIdSet.contains(sessionId)) {
					context.getSession().clear();
				}
			}
		};
	}
}
