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

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.persistence.GenerationType;

import org.seasar.extension.jdbc.EntityMeta;
import org.seasar.extension.jdbc.JdbcContext;
import org.seasar.extension.jdbc.PropertyMeta;
import org.seasar.extension.jdbc.SqlLogger;
import org.seasar.extension.jdbc.exception.IdGenerationFailedRuntimeException;
import org.seasar.extension.jdbc.manager.JdbcManagerImplementor;
import org.seasar.framework.util.PreparedStatementUtil;
import org.seasar.framework.util.StringUtil;

/**
 * {@link GenerationType#IDENTITY}方式で識別子の値を自動生成するIDジェネレータです。
 * 
 * @author koichik
 */
public class IdentityIdGenerator extends AbstractIdGenerator {

    /**
     * インスタンスを構築します。
     * 
     * @param entityMeta
     *            エンティティのメタデータ
     * @param propertyMeta
     *            識別子を表すプロパティのメタデータ
     */
    public IdentityIdGenerator(final EntityMeta entityMeta,
            final PropertyMeta propertyMeta) {
        super(entityMeta, propertyMeta);
    }

    public boolean supportBatch(final JdbcManagerImplementor jdbcManager) {
        return false;
    }

    public boolean useGetGeneratedKeys(final JdbcManagerImplementor jdbcManager) {
        return jdbcManager.getDialect().supportsGetGeneratedKeys();
    }

    public boolean isInsertInto(final JdbcManagerImplementor jdbcManager) {
        return jdbcManager.getDialect().isInsertIdentityColumn();
    }

    public Object preInsert(final JdbcManagerImplementor jdbcManager,
            final Object entity, final SqlLogger sqlLogger) {
        return null;
    }

    public void postInsert(final JdbcManagerImplementor jdbcManager,
            final Object entity, final Statement statement,
            final SqlLogger sqlLogger) {
        final long id;
        if (jdbcManager.getDialect().supportsGetGeneratedKeys()) {
            id = getGeneratedId(statement);
        } else {
            id = getGeneratedId(jdbcManager, sqlLogger);
        }
        setId(entity, id);
    }

    /**
     * {@link Statement#getGeneratedKeys()}を使用して自動生成された識別子の値を取得して返します。
     * 
     * @param statement
     *            INSERT文を実行した{@link Statement}
     * @return 自動生成された識別子の値
     */
    protected long getGeneratedId(final Statement statement) {
        try {
            final ResultSet rs = statement.getGeneratedKeys();
            return getGeneratedId(rs);
        } catch (final SQLException e) {
            throw new IdGenerationFailedRuntimeException(entityMeta.getName(),
                    propertyMeta.getName(), e);
        }
    }

    /**
     * DBMS固有のSQLを使用して自動生成された識別子の値を取得して返します。
     * 
     * @param jdbcManager
     *            内部的なJDBCマネージャ
     * @param sqlLogger
     *            SQLロガー
     * @return 自動生成された識別子の値
     */
    protected long getGeneratedId(final JdbcManagerImplementor jdbcManager,
            final SqlLogger sqlLogger) {
        final String sql = jdbcManager.getDialect().getIdentitySelectString(
                toQualifiedName(entityMeta.getTableMeta().getSchema(),
                        entityMeta.getTableMeta().getName()),
                propertyMeta.getColumnMeta().getName());
        sqlLogger.logSql(sql);
        final JdbcContext jdbcContext = jdbcManager.getJdbcContext();
        try {
            final PreparedStatement ps = jdbcContext.getPreparedStatement(sql);
            final ResultSet rs = PreparedStatementUtil.executeQuery(ps);
            return getGeneratedId(rs);
        } finally {
            if (!jdbcContext.isTransactional()) {
                jdbcContext.destroy();
            }
        }
    }

    /**
     * スキーマ名が指定された場合は修飾された名前を返します。
     * 
     * @param schema
     *            スキーマ名
     * @param tableName
     *            テーブル名
     * @return 修飾された名前
     */
    protected String toQualifiedName(String schema, String tableName) {
        if (StringUtil.isEmpty(schema)) {
            return tableName;
        }
        return schema + "." + tableName;
    }

}
