/*
 * Copyright 2004-2015 the Seasar Foundation and the Others.
 *
 * 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.seasar.extension.jdbc.gen.internal.command;

import java.sql.Connection;
import java.sql.DatabaseMetaData;

import org.seasar.extension.jdbc.JdbcManager;
import org.seasar.extension.jdbc.gen.ProductInfo;
import org.seasar.extension.jdbc.gen.command.Command;
import org.seasar.extension.jdbc.gen.dialect.GenDialect;
import org.seasar.extension.jdbc.gen.dialect.GenDialectRegistry;
import org.seasar.extension.jdbc.gen.exception.CommandFailedRuntimeException;
import org.seasar.extension.jdbc.gen.internal.factory.Factory;
import org.seasar.extension.jdbc.gen.internal.factory.FactoryImpl;
import org.seasar.extension.jdbc.gen.internal.util.ReflectUtil;
import org.seasar.extension.jdbc.gen.internal.util.SingletonS2ContainerFactorySupport;
import org.seasar.extension.jdbc.manager.JdbcManagerImplementor;
import org.seasar.extension.jdbc.util.ConnectionUtil;
import org.seasar.extension.jdbc.util.DataSourceUtil;
import org.seasar.extension.jdbc.util.DatabaseMetaDataUtil;
import org.seasar.framework.beans.BeanDesc;
import org.seasar.framework.beans.PropertyDesc;
import org.seasar.framework.beans.factory.BeanDescFactory;
import org.seasar.framework.container.SingletonS2Container;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;
import org.seasar.framework.log.Logger;

/**
 * コマンドの抽象クラスです。
 * 
 * @author taedium
 */
public abstract class AbstractCommand implements Command {

    /** 設定ファイルのパス */
    protected String configPath = "s2jdbc.dicon";

    /** 環境名 */
    protected String env = "ut";

    /** {@link JdbcManager}のコンポーネント名 */
    protected String jdbcManagerName = "jdbcManager";

    /** {@link Factory}の実装クラス名 */
    protected String factoryClassName = FactoryImpl.class.getName();

    /** {@link SingletonS2ContainerFactory}のサポート */
    protected SingletonS2ContainerFactorySupport containerFactorySupport;

    /** 内部的なJDBCマネージャ */
    protected JdbcManagerImplementor jdbcManager;

    /** S2JDBC-Genのインタフェースの実装を作成するファクトリ */
    protected Factory factory;

    /**
     * インスタンスを構築します。
     */
    public AbstractCommand() {
    }

    /**
     * 設定ファイルのパスを返します。
     * 
     * @return 設定ファイルのパス
     */
    public String getConfigPath() {
        return configPath;
    }

    /**
     * 設定ファイルのパスを設定します。
     * 
     * @param configPath
     *            設定ファイルのパス
     */
    public void setConfigPath(String configPath) {
        this.configPath = configPath;
    }

    /**
     * 環境名を返します。
     * 
     * @return 環境名
     */
    public String getEnv() {
        return env;
    }

    /**
     * 環境名を設定します。
     * 
     * @param env
     *            環境名
     */
    public void setEnv(String env) {
        this.env = env;
    }

    /**
     * {@link JdbcManager}のコンポーネント名を返します。
     * 
     * @return {@link JdbcManager}のコンポーネント名
     */
    public String getJdbcManagerName() {
        return jdbcManagerName;
    }

    /**
     * {@link JdbcManager}のコンポーネント名を設定します。
     * 
     * @param jdbcManagerName
     *            {@link JdbcManager}のコンポーネント名
     */
    public void setJdbcManagerName(String jdbcManagerName) {
        this.jdbcManagerName = jdbcManagerName;
    }

    /**
     * {@link Factory}の実装クラス名を返します。
     * 
     * @return {@link Factory}の実装クラス名
     */
    public String getFactoryClassName() {
        return factoryClassName;
    }

    /**
     * {@link Factory}の実装クラス名を設定します。
     * 
     * @param factoryClassName
     *            {@link Factory}の実装クラス名
     */
    public void setFactoryClassName(String factoryClassName) {
        this.factoryClassName = factoryClassName;
    }

    public final void execute() {
        ProductInfo info = ProductInfo.getInstance();
        getLogger().log("IS2JDBCGen0008",
                new Object[] { info.getName(), info.getVersion() });
        String commandClassName = getClass().getName();
        getLogger().log("DS2JDBCGen0003", new Object[] { commandClassName });
        logWritableProperties();
        validate();
        init();
        try {
            doExecute();
        } catch (Throwable t) {
            throw new CommandFailedRuntimeException(t, commandClassName);
        } finally {
            destroy();
        }
        getLogger().log("DS2JDBCGen0008", new Object[] { commandClassName });
    }

    /**
     * 設定可能なプロパティの値をログ出力します。
     */
    protected void logWritableProperties() {
        BeanDesc beanDesc = BeanDescFactory.getBeanDesc(getClass());
        for (int i = 0; i < beanDesc.getPropertyDescSize(); i++) {
            PropertyDesc propertyDesc = beanDesc.getPropertyDesc(i);
            if (propertyDesc.hasWriteMethod()) {
                getLogger().log(
                        "DS2JDBCGen0001",
                        new Object[] { propertyDesc.getPropertyName(),
                                propertyDesc.getValue(this) });
            }
        }
    }

    /**
     * 検証します。
     */
    protected final void validate() {
        doValidate();
    }

    /**
     * 初期化します。
     */
    protected final void init() {
        factory = ReflectUtil.newInstance(Factory.class, factoryClassName);
        containerFactorySupport = new SingletonS2ContainerFactorySupport(
                configPath, env);
        containerFactorySupport.init();
        jdbcManager = SingletonS2Container.getComponent(jdbcManagerName);
        doInit();
    }

    /**
     * 破棄します。
     */
    protected final void destroy() {
        doDestroy();
        if (containerFactorySupport != null) {
            containerFactorySupport.destory();
        }
    }

    /**
     * RDBMSとRDBMSに対する方言をログ出力します。
     * 
     * @param dialect
     */
    protected void logRdbmsAndGenDialect(GenDialect dialect) {
        getLogger().log("DS2JDBCGen0005",
                new Object[] { getRdbmsName(), dialect.getClass().getName() });
    }

    /**
     * RDBMSの名前を返します。
     * 
     * @return RDBMSの名前
     */
    protected String getRdbmsName() {
        Connection conn = DataSourceUtil.getConnection(jdbcManager
                .getDataSource());
        try {
            DatabaseMetaData metaData = ConnectionUtil.getMetaData(conn);
            return DatabaseMetaDataUtil.getDatabaseProductName(metaData);
        } finally {
            ConnectionUtil.close(conn);
        }
    }

    /**
     * {@link GenDialect}の実装クラスを返します。
     * 
     * @param genDialectClassName
     *            {@link GenDialect}の実装クラス名
     * @return {@link GenDialect}の実装クラス
     */
    protected GenDialect getGenDialect(String genDialectClassName) {
        if (genDialectClassName != null) {
            return ReflectUtil.newInstance(GenDialect.class,
                    genDialectClassName);
        }
        return GenDialectRegistry.getGenDialect(jdbcManager.getDialect());
    }

    /**
     * サブクラスで検証します。
     */
    protected abstract void doValidate();

    /**
     * サブクラスで初期化します。
     */
    protected abstract void doInit();

    /**
     * サブクラスで実行します。
     * 
     * @throws Throwable
     */
    protected abstract void doExecute() throws Throwable;

    /**
     * サブクラスで破棄します。
     */
    protected abstract void doDestroy();

    /**
     * ロガーを返します。
     * 
     * @return ロガー
     */
    protected abstract Logger getLogger();

}
