/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (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.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * 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 com.queplix.core.modules.config.utils.db;

import com.queplix.core.error.GenericSystemException;
import com.queplix.core.jxb.entity.types.SqlSType;
import com.queplix.core.jxb.entityschema.Property;
import com.queplix.core.modules.config.utils.EntityDdobjectInfoVO;
import com.queplix.core.modules.config.utils.EntityFieldInfoVO;
import com.queplix.core.utils.log.AbstractLogger;
import com.queplix.core.utils.sql.SqlWrapper;
import com.queplix.core.utils.sql.SqlWrapperFactory;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Generic entity schema implementation.
 *
 * @author [ALB] Baranov Andrey
 * @version $Revision: 1.1.1.1 $ $Date: 2005/09/12 15:30:17 $
 */

public class EntitySchemaImpl
        extends AbstractLogger
        implements DBEntitySchema {

    // --------------------------------------------------------------- fields/constants

    /**
     * Property - SqlWrapper name.
     */
    protected static final String SQLWRAPPER_PROPERTY = "sqlwrapper";

    // Schema name.
    private String schemaName;

    // SqlWrapper instance.
    private SqlWrapper sqlWrapper;

    // --------------------------------------------------------------- interface implementation

    /*
     * No javadoc
     * @see EntitySchema#init
     */

    public void init(String schemaName, Property[] properties) {

        this.schemaName = schemaName;

        // Read property and initialize SqlWrapper
        if(properties != null) {
            for(int i = 0; i < properties.length; i++) {
                Property p = properties[i];
                String name = p.getName();
                String value = p.getValue();

                if(name.equalsIgnoreCase(SQLWRAPPER_PROPERTY)) {
                    this.sqlWrapper = SqlWrapperFactory.getSqlWrapper(value);
                }
            }
        }
        if(sqlWrapper == null) {
            throw new GenericSystemException("Cannot find SqlWrapper instance");
        }
    }

    /*
     * No javadoc
     * @see EntitySchema#getSchemaName
     */
    public String getSchemaName() {
        return schemaName;
    }

    /*
     * No javadoc
     * @see EntitySchema#getEntityDdobjectInfo( String )
     */
    public EntityDdobjectInfoVO getEntityDdobjectInfo(String dbobject) {

        EntityDdobjectInfoVO tableInfo = null;
        Connection con = null;

        try {
            con = sqlWrapper.doConnection();
            tableInfo = getEntityDdobjectInfo(con, dbobject);

        } catch (GenericSystemException ex) {
            ERROR(ex);
            throw ex;

        } catch (Exception ex) {
            ERROR(ex);
            throw new GenericSystemException(
                    "Cannot retrive metadata for table '" +
                            dbobject + "': " + ex.getMessage(), ex);
        } finally {
            sqlWrapper.closeConnection(con);
        }

        return tableInfo;
    }

    /*
     * No javadoc
     * @see EntitySchema#getEntityDdobjectInfo()
     */
    public Collection getEntityDdobjectInfo() {

        List ret = new ArrayList();
        Connection con = null;

        try {
            con = sqlWrapper.doConnection();

            DatabaseMetaData dmd = con.getMetaData();
            String catalog = con.getCatalog();
            String schema = sqlWrapper.getCurrentSchema(con);

            if(getLogger().isDebugEnabled()) {
                DEBUG("   Catalog '" + catalog +
                        "', Schema '" + schema + "'");
            }

            // Get list of tables.
            ResultSet rs = dmd.getTables(catalog, schema, null, new String[]{
                    "TABLE",
                    "VIEW"
            });
            while(rs.next()) {
                String dbobject = rs.getString("TABLE_NAME");

                DEBUG("Found table '" + dbobject + "'");

                // Get dbobject info.
                ret.add(getEntityDdobjectInfo(con, dbobject));
            }

            if(getLogger().isDebugEnabled()) {
                DEBUG("   Found " + ret.size() + " tables/view");
            }

        } catch (GenericSystemException ex) {
            ERROR(ex);
            throw ex;

        } catch (Exception ex) {
            ERROR(ex);
            throw new GenericSystemException(
                    "Cannot retrive metadata: " + ex.getMessage(), ex);

        } finally {
            sqlWrapper.closeConnection(con);
        }

        return (ret.size() == 0) ? null:ret;
    }

    /*
     * No javadoc
     * @see DBEntitySchema#getSqlWrapper
     */
    public SqlWrapper getSqlWrapper() {
        return sqlWrapper;
    }

    // --------------------------------------------------------------- private methods

    //
    // Initialize EntityDdobjectInfoVO object.
    //

    private EntityDdobjectInfoVO getEntityDdobjectInfo(Connection con,
                                                       String dbobject)
            throws SQLException {

        // Init.
        EntityDdobjectInfoVO tableInfo = new EntityDdobjectInfoVO(dbobject);

        DatabaseMetaData dmd = con.getMetaData();
        String catalog = con.getCatalog();
        String schema = sqlWrapper.getCurrentSchema(con);

        if(getLogger().isDebugEnabled()) {
            DEBUG("   Catalog '" + catalog +
                    "', Schema '" + schema + "'");
        }

        // 1) Get pkey list.
        ResultSet rs = dmd.getPrimaryKeys(catalog, schema, dbobject);
        while(rs.next()) {
            tableInfo.addPkey(rs.getString("COLUMN_NAME"));
        }
        rs.close();

        if(getLogger().isDebugEnabled()) {
            DEBUG("   Found " + tableInfo.getPkeySize() +
                    " public keys for table '" + dbobject + "'");
        }

        // 2) Get fkey list.
        rs = dmd.getExportedKeys(catalog, schema, dbobject);
        while(rs.next()) {
            String pkColumnName = rs.getString("PKCOLUMN_NAME");
            String fkTableName = rs.getString("FKTABLE_NAME");
            String fkColumnName = rs.getString("FKCOLUMN_NAME");
            tableInfo.addFkey(new EntityDdobjectInfoVO.FK(pkColumnName,
                    fkTableName, fkColumnName));
        }
        rs.close();

        if(getLogger().isDebugEnabled()) {
            DEBUG("   Found " + tableInfo.getFkeySize() +
                    " foreign keys for table '" + dbobject + "'");
        }

        // 3) Get field list.
        rs = dmd.getColumns(catalog, schema, dbobject, null);
        while(rs.next()) {
            tableInfo.addField(nextEntityFieldInfo(rs));
        }
        rs.close();

        if(getLogger().isDebugEnabled()) {
            DEBUG("   Found " + tableInfo.getFieldSize() +
                    " columns for table '" + dbobject + "'");
        }

        return tableInfo;
    }

    //
    // Initialize EntityFieldInfoVO object.
    //
    private EntityFieldInfoVO nextEntityFieldInfo(ResultSet rs)
            throws SQLException {

        String columnName = rs.getString("COLUMN_NAME");
        int dataType = rs.getInt("DATA_TYPE");
        int columnSize = rs.getInt("COLUMN_SIZE");
        boolean isNullable = rs.getString("IS_NULLABLE").equalsIgnoreCase(
                "YES");

        // Build EntityFieldInfoVO.
        EntityFieldInfoVO vo = new EntityFieldInfoVO(columnName, columnSize,
                isNullable);

        // Set SqlSType.
        SqlSType sqlType = sqlWrapper.typeMapping(dataType, columnSize);
        vo.setSqlType(sqlType);

        return vo;
    }
}
