package org.seasar.struts;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.util.MessageResources;
import org.seasar.framework.container.ComponentNotFoundRuntimeException;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;
import org.seasar.framework.util.ClassUtil;

/**
 * S2と連動したActionのインスタンスを生成する為のクラスです。
 * 
 * @author higa
 * @author Satoshi Kimura
 */
public final class ActionUtil {

    private ActionUtil() {
    }

    /**
     * @deprecated use createActionFromClassName(String)
     * @see #createActionWithClassName(String)
     */
    public static Action createAction(String className) {
        return createActionWithClassName(className);
    }
    /**
     * {@link #createActionWithClassName(String, ActionServlet) }と同等のメソッドです。 <br>
     * 違いは、Actionのインスタンスに、ActionServletをセットしないことです。
     * 
     * @see #createActionWithClassName(String, ActionServlet)
     */
    public static Action createActionWithClassName(String className) {
        return createActionWithClassName(className, null);
    }
    /**
     * S2コンテナからActionクラスのインスタンスを取得します。 <br>
     * S2コンテナに取得対象のクラスが登録されていない場合、インスタンス取得の前に、 S2コンテナにActionクラスの登録を行います。
     * 
     * @param className 取得対象のクラス名
     * @param servlet ActionにセットするActionServlet。nullの場合は、nullはセットされません。
     * @return S2コンテナから取得したActionクラスのインスタンス
     */
    public static Action createActionWithClassName(String className, ActionServlet servlet) {
        S2Container container = SingletonS2ContainerFactory.getContainer();
        Class clazz = ClassUtil.forName(className);
        if (false == container.hasComponentDef(clazz)) {
            regist(container, clazz);
        }
        Action action = (Action) container.getComponent(clazz);
        setServlet(action, servlet);
        return action;
    }

    /**
     * {@link #createActionWithComponentName(String, ActionServlet) }と同等のメソッドです。 <br>
     * 違いは、Actionのインスタンスに、ActionServletをセットしないことです。
     * 
     * @see #createActionWithComponentName(String, ActionServlet)
     */
    public static Action createActionWithComponentName(String componentName) throws ComponentNotFoundRuntimeException {
        return createActionWithComponentName(componentName, null);
    }
    /**
     * S2コンテナからActionクラスのインスタンスを取得します。 <br>
     * 
     * @param componentName 取得対象のコンポーネント名
     * @param servlet ActionにセットするActionServlet。nullの場合は、nullはセットされません。
     * @return S2コンテナから取得したActionクラスのインスタンス
     * @throws ComponentNotFoundRuntimeException コンポーネントが見つからない場合
     */
    public static Action createActionWithComponentName(String componentName, ActionServlet servlet)
            throws ComponentNotFoundRuntimeException {
        S2Container container = SingletonS2ContainerFactory.getContainer();
        Action action = (Action) container.getComponent(componentName);
        setServlet(action, servlet);
        return action;
    }

    /**
     * ActionMappingを参照して、コンポーネント名からインスタンスを生成するのか、クラス名からインスタンスを生成するのかを判断して、 Actionのインスタンスを生成する。
     * 
     * @param request 処理しているHTTPリクエスト
     * @param response 生成中のHTTPレスポンス
     * @param mapping Actionのインスタンスを生成するために使用する
     * @param log ログ
     * @param internal メッセージリソース
     * @param servlet 生成されるActionに関連づけられているServletのインスタンス
     * @return S2コンテナから取得したActionクラスのインスタンス
     * @throws IOException インスタンス生成に失敗した場合に、レスポンスを操作時に発生する例外
     */
    public static Action processActionCreate(HttpServletRequest request, HttpServletResponse response,
            ActionMapping mapping, Log log, MessageResources internal, ActionServlet servlet) throws IOException {

        Action instance = null;
        try {
            if (isCreateActionWithComponentName(mapping)) {
                S2Container container = SingletonS2ContainerFactory.getContainer();
                ComponentNameCreator componentNameCreator = (ComponentNameCreator) container
                        .getComponent(ComponentNameCreator.class);
                String componentName = componentNameCreator.createComponentName(container, mapping);
                instance = createActionWithComponentName(componentName, servlet);
            } else {
                String className = mapping.getType();
                if (log.isDebugEnabled()) {
                    log.debug(" Looking for Action instance for class " + className);
                }
                instance = createActionWithClassName(className, servlet);
            }
        } catch (Exception e) {
            String internalMessage = internal.getMessage("actionCreate", mapping.getPath());

            log.error(internalMessage, e);

            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, internalMessage);

            return null;
        }
        return instance;
    }

    private static void regist(S2Container container, Class clazz) {
        synchronized (container) {
            if (false == container.hasComponentDef(clazz)) {
                container.register(clazz);
            }
        }
    }

    private static void setServlet(Action action, ActionServlet servlet) {
        if (servlet != null) {
            action.setServlet(servlet);
        }
    }

    private static boolean isCreateActionWithComponentName(ActionMapping mapping) {
        String type = mapping.getType();
        String forward = mapping.getForward();
        String include = mapping.getInclude();
        if (type == null && forward == null && include == null) {
            return true;
        } else {
            return false;
        }
    }
}