/*
 * 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.io.File;
import java.util.ArrayList;
import java.util.List;

import javax.transaction.UserTransaction;

import org.seasar.extension.jdbc.gen.command.Command;
import org.seasar.extension.jdbc.gen.dialect.GenDialect;
import org.seasar.extension.jdbc.gen.internal.exception.RequiredPropertyEmptyRuntimeException;
import org.seasar.extension.jdbc.gen.sql.SqlExecutionContext;
import org.seasar.extension.jdbc.gen.sql.SqlFileExecutor;
import org.seasar.extension.jdbc.gen.sql.SqlUnitExecutor;
import org.seasar.framework.container.SingletonS2Container;
import org.seasar.framework.log.Logger;

/**
 * SQLを実行する{@link Command}の実装です。
 * <p>
 * このコマンドは、指定されたSQLファイルに記述された複数のSQLステートメントまたはSQLブロックをデータベースに対し実行します。SQLファイルには、1
 * 行コメントやブロックコメントを記述することもできます。コメントはSQLとは認識されず実行されません。
 * </p>
 * 
 * @author taedium
 */
public class ExecuteSqlCommand extends AbstractCommand {

    /** ロガー */
    protected static Logger logger = Logger.getLogger(ExecuteSqlCommand.class);

    /** 実行するSQLファイルのリスト */
    protected List<File> sqlFileList = new ArrayList<File>();

    /** SQLファイルのエンコーディング */
    protected String sqlFileEncoding = "UTF-8";

    /** SQLステートメントの区切り文字 */
    protected char statementDelimiter = ';';

    /** SQLブロックの区切り文字 */
    protected String blockDelimiter = null;

    /** エラー発生時に処理を中止する場合{@code true} */
    protected boolean haltOnError = true;

    /** すべてのSQLを単一のトランザクションで実行する場合{@code true}、そうでない場合{@code false} */
    protected boolean transactional = false;

    /** {@link GenDialect}の実装クラス名 */
    protected String genDialectClassName = null;

    /** 方言 */
    protected GenDialect dialect;

    /** ユーザトランザクション */
    protected UserTransaction userTransaction;

    /** SQLファイルの実行者 */
    protected SqlFileExecutor sqlFileExecutor;

    /** SQLのひとまとまりの処理の実行者 */
    protected SqlUnitExecutor sqlUnitExecutor;

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

    /**
     * SQLブロックの区切り文字を返します。
     * 
     * @return SQLブロックの区切り文字
     */
    public String getBlockDelimiter() {
        return blockDelimiter;
    }

    /**
     * SQLブロックの区切り文字を設定します。
     * 
     * @param blockDelimiter
     *            SQLブロックの区切り文字
     */
    public void setBlockDelimiter(String blockDelimiter) {
        this.blockDelimiter = blockDelimiter;
    }

    /**
     * エラー発生時に処理を中止する場合{@code true}を返します。
     * 
     * @return エラー発生時に処理を中止する場合{@code true}
     */
    public boolean isHaltOnError() {
        return haltOnError;
    }

    /**
     * エラー発生時に処理を中止する場合{@code true}を設定します。
     * 
     * @param haltOnError
     *            エラー発生時に処理を中止する場合{@code true}
     */
    public void setHaltOnError(boolean haltOnError) {
        this.haltOnError = haltOnError;
    }

    /**
     * SQLファイルのエンコーディングを返します。
     * 
     * @return SQLファイルのエンコーディング
     */
    public String getSqlFileEncoding() {
        return sqlFileEncoding;
    }

    /**
     * SQLファイルのエンコーディングを設定します。
     * 
     * @param sqlFileEncoding
     *            SQLファイルのエンコーディング
     */
    public void setSqlFileEncoding(String sqlFileEncoding) {
        this.sqlFileEncoding = sqlFileEncoding;
    }

    /**
     * 実行するSQLファイルのリストを返します。
     * 
     * @return 実行するSQLファイルのリスト
     */
    public List<File> getSqlFileList() {
        return sqlFileList;
    }

    /**
     * 実行するSQLファイルのリストを設定します。
     * 
     * @param sqlFileList
     *            実行するSQLファイルのリスト
     */
    public void setSqlFileList(List<File> sqlFileList) {
        this.sqlFileList = sqlFileList;
    }

    /**
     * すべてのSQLを単一のトランザクションで実行する場合{@code true}、そうでない場合{@code false}を返します。
     * 
     * @return すべてのSQLを単一のトランザクションで実行する場合{@code true}、そうでない場合{@code false}
     */
    public boolean isTransactional() {
        return transactional;
    }

    /**
     * すべてのSQLを単一のトランザクションで実行する場合{@code true}、そうでない場合{@code false}を設定します。
     * 
     * @param transactional
     *            すべてのSQLを単一のトランザクションで実行する場合{@code true}、そうでない場合{@code false}
     */
    public void setTransactional(boolean transactional) {
        this.transactional = transactional;
    }

    /**
     * SQLステートメントの区切り文字を返します。
     * 
     * @return SQLステートメントの区切り文字
     */
    public char getStatementDelimiter() {
        return statementDelimiter;
    }

    /**
     * SQLステートメントの区切り文字を設定します。
     * 
     * @param statementDelimiter
     *            SQLステートメントの区切り文字
     */
    public void setStatementDelimiter(char statementDelimiter) {
        this.statementDelimiter = statementDelimiter;
    }

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

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

    @Override
    protected void doValidate() {
        if (sqlFileList.isEmpty()) {
            throw new RequiredPropertyEmptyRuntimeException("sqlFileList");
        }
    }

    @Override
    protected void doInit() {
        dialect = getGenDialect(genDialectClassName);
        if (transactional) {
            userTransaction = SingletonS2Container
                    .getComponent(UserTransaction.class);
        }
        sqlFileExecutor = createSqlFileExecutor();
        sqlUnitExecutor = createSqlUnitExecutor();

        logRdbmsAndGenDialect(dialect);
    }

    @Override
    protected void doExecute() throws Throwable {
        sqlUnitExecutor.execute(new SqlUnitExecutor.Callback() {

            public void execute(SqlExecutionContext context) {
                for (File sqlFile : sqlFileList) {
                    sqlFileExecutor.execute(context, sqlFile);
                }
            }
        });
    }

    @Override
    protected void doDestroy() {
    }

    /**
     * {@link SqlFileExecutor}の実装を返します。
     * 
     * @return {@link SqlFileExecutor}の実装
     */
    protected SqlFileExecutor createSqlFileExecutor() {
        return factory.createSqlFileExecutor(this, dialect, sqlFileEncoding,
                statementDelimiter, blockDelimiter);
    }

    /**
     * {@link SqlUnitExecutor}の実装を返します。
     * 
     * @return {@link SqlUnitExecutor}の実装
     */
    protected SqlUnitExecutor createSqlUnitExecutor() {
        return factory.createSqlUnitExecutor(this, jdbcManager.getDataSource(),
                userTransaction, haltOnError);
    }

    @Override
    protected Logger getLogger() {
        return logger;
    }

}
