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

import java.util.concurrent.ConcurrentMap;

import woolpack.utils.CheckUtils;
import woolpack.utils.concurrent.Acquirable;
import woolpack.utils.concurrent.AcquirableFactory;

/**
 * セッション毎に実行権を制御する{@link DomExpression}。 HTML
 * フォームを使用する際はクライアントからの並行なリクエストをエラーにする可能性があるため使用に注意する必要がある。
 * 適用しているパターン：Proxy、Before After、Balking。
 * 
 * @author nakamura
 * 
 */
public class DoAcquireSession implements DomExpression {
	/**
	 * セマフォを格納する{@link DomContext#getSession()}のキーのデフォルト値。
	 */
	public static final String KEY = "woolpack.dom.TRY_SEMAPHORE";

	private final String key;

	private final AcquirableFactory factory;

	private final DomExpression trueExpression;

	private final DomExpression falseExpression;

	/**
	 * コンストラクタ。
	 * 
	 * @param key
	 *            {@link Acquirable}を格納する{@link DomContext#getSession()}のキー。
	 * @param factory
	 *            {@link Acquirable}のファクトリ。
	 * @param trueExpression
	 *            実行権の取得に成功した場合の委譲先。
	 * @param falseExpression
	 *            実行権の取得に失敗した場合の委譲先。
	 * @throws NullPointerException
	 *             引数のいずれかが null の場合。
	 */
	public DoAcquireSession(final String key, final AcquirableFactory factory,
			final DomExpression trueExpression,
			final DomExpression falseExpression) {
		CheckUtils.checkNotEmpty(key);
		CheckUtils.checkNotNull(factory);
		CheckUtils.checkNotNull(trueExpression);
		CheckUtils.checkNotNull(falseExpression);

		this.key = key;
		this.factory = factory;
		this.trueExpression = trueExpression;
		this.falseExpression = falseExpression;
	}

	/**
	 * コンストラクタ。
	 * 
	 * @param factory
	 *            {@link Acquirable}のファクトリ。
	 * @param trueExpression
	 *            実行権の取得に成功した場合の委譲先。
	 * @param falseExpression
	 *            実行権の取得に失敗した場合の委譲先。
	 * @throws NullPointerException
	 *             引数のいずれかが null の場合。
	 */
	public DoAcquireSession(final AcquirableFactory factory,
			final DomExpression trueExpression,
			final DomExpression falseExpression) {
		this(KEY, factory, trueExpression, falseExpression);
	}

	private Acquirable createPerSession(
			final ConcurrentMap<String, Object> session) {
		while (true) {
			final Acquirable acquirable = (Acquirable) session.get(key);
			if (acquirable != null) {
				return acquirable;
			}
			session.putIfAbsent(key, factory.newInstance());
		}
	}

	public void interpret(final DomContext context) {
		final Acquirable acquirable = createPerSession(context.getSession());
		if (acquirable.acquire()) {
			try {
				trueExpression.interpret(context);
			} finally {
				acquirable.release();
			}
		} else {
			falseExpression.interpret(context);
		}
	}
}
