/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.as.cmp.jdbc.metadata;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jboss.as.cmp.CmpConfig;
import org.jboss.as.cmp.CmpMessages;
import org.jboss.as.cmp.jdbc.metadata.parser.ParsedCmpField;
import org.jboss.as.cmp.jdbc.metadata.parser.ParsedEntity;
import org.jboss.as.cmp.jdbc.metadata.parser.ParsedQuery;
import org.jboss.metadata.ejb.spec.CMPFieldMetaData;
import org.jboss.metadata.ejb.spec.EntityBeanMetaData;
import org.jboss.metadata.ejb.spec.QueryMetaData;

import static org.jboss.as.cmp.CmpMessages.MESSAGES;

/**
 * This immutable class contains information about an entity
 *
 * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
 * @author <a href="sebastien.alborini@m4x.org">Sebastien Alborini</a>
 * @author <a href="mailto:dirk@jboss.de">Dirk Zimmermann</a>
 * @author <a href="mailto:loubyansky@hotmail.com">Alex Loubyansky</a>
 * @author <a href="mailto:heiko.rupp@cellent.de">Heiko W. Rupp</a>
 * @version $Revision: 81030 $
 */
public final class JDBCEntityMetaData {
    /**
     * application metadata in which this entity is defined
     */
    private final JDBCApplicationMetaData jdbcApplication;

    /**
     * data source name in jndi
     */
    private final String dataSourceName;

    /**
     * type mapping used for this entity
     */
    private final String dataSourceMappingName;

    /**
     * the name of this entity
     */
    private final String entityName;

    /**
     * the abstract schema name of this entity
     */
    private final String abstractSchemaName;

    /**
     * the implementation class of this entity
     */
    private final Class<?> entityClass;

    /**
     * the home class of this entity
     */
    private final Class<?> homeClass;

    /**
     * the remote class of this entity
     */
    private final Class<?> remoteClass;

    /**
     * the local home class of this entity
     */
    private final Class<?> localHomeClass;

    /**
     * the local class of this entity
     */
    private final Class<?> localClass;

    /**
     * Does this entity use cmp 1.x?
     */
    private final boolean isCMP1x;

    /**
     * the name of the table to which this entity is persisted
     */
    private final String tableName;

    /**
     * Should we try and create the table when deployed?
     */
    private final boolean createTable;

    /**
     * indicates if CREATE TABLE IF NOT EXISTS sql dialect is supported
     */
    private final boolean createTableIfNotExistsSupported;

    /**
     * Should we drop the table when undeployed?
     */
    private final boolean removeTable;

    /**
     * Should we alter the table when deployed?
     */
    private final boolean alterTable;

    /**
     * What command should be issued directly after creation
     * of a table?
     */
    private final List<String> tablePostCreateCmd = new ArrayList<String>();

    /**
     * Should we use 'SELECT ... FOR UPDATE' syntax when loading?
     */
    private final boolean rowLocking;

    /**
     * Is this entity read-only?
     */
    private final boolean readOnly;

    /**
     * how long is a read valid
     */
    private final Integer readTimeOut;

    /**
     * Should the table have a primary key constraint?
     */
    private final boolean primaryKeyConstraint;

    /**
     * the java class of the primary key
     */
    private final Class<?> primaryKeyClass;

    /**
     * the name of the primary key field or null if the primary key field
     * is multivalued
     */
    private final String primaryKeyFieldName;

    /**
     * Map of the cmp fields of this entity by field name.
     */
    private final Map<String, JDBCCMPFieldMetaData> cmpFieldsByName = new HashMap<String, JDBCCMPFieldMetaData>();
    private final List<JDBCCMPFieldMetaData> cmpFields = new ArrayList<JDBCCMPFieldMetaData>();

    /**
     * A map of all the load groups by name.
     */
    private final Map<String, List<String>> loadGroups = new HashMap<String, List<String>>();

    /**
     * The fields which should always be loaded when an entity of this type
     * is loaded.
     */
    private final String eagerLoadGroup;

    /**
     * A list of groups (also lists) of the fields that should be lazy
     * loaded together.
     */
    private final List<String> lazyLoadGroups = new ArrayList<String>();

    /**
     * Map of the queries on this entity by the Method that invokes the query.
     */
    private final Map<Method, JDBCQueryMetaData> queries = new HashMap<Method, JDBCQueryMetaData>();

    /**
     * The factory used to used to create query meta data
     */
    private final JDBCQueryMetaDataFactory queryFactory = new JDBCQueryMetaDataFactory(this);

    /**
     * The read ahead meta data
     */
    private final JDBCReadAheadMetaData readAhead;

    /**
     * clean-read-ahead-on-load
     * Since 3.2.5RC1, previously read ahead cache was cleaned after loading.
     */
    private final boolean cleanReadAheadOnLoad;

    /**
     * The maximum number of read ahead lists that can be tracked for this
     * entity.
     */
    private final int listCacheMax;

    /**
     * The number of entities to read in one round-trip to the
     * underlying data store.
     */
    private final int fetchSize;

    /**
     * entity command meta data
     */
    private final JDBCEntityCommandMetaData entityCommand;


    /**
     * optimistic locking metadata
     */
    private final JDBCOptimisticLockingMetaData optimisticLocking;


    /**
     * audit metadata
     */
    private final JDBCAuditMetaData audit;

    private final Class<?> qlCompiler;

    /**
     * throw runtime exception metadata
     */
    private final boolean throwRuntimeExceptions;

    private final CmpConfig cmpConfig;


    public JDBCEntityMetaData(final JDBCApplicationMetaData jdbcApplication) {
        this.jdbcApplication = jdbcApplication;
        listCacheMax = 1000;
        fetchSize = 0;
        entityName = null;
        entityClass = null;
        primaryKeyClass = null;
        isCMP1x = false;
        primaryKeyFieldName = null;
        homeClass = null;
        remoteClass = null;
        localHomeClass = null;
        localClass = null;
        abstractSchemaName = null;
        dataSourceName = null;
        dataSourceMappingName = null;
        tableName = null;
        createTable = false;
        createTableIfNotExistsSupported = false;
        removeTable = false;
        alterTable = false;
        readOnly = false;
        readTimeOut = -1;
        rowLocking = false;
        primaryKeyConstraint = false;
        entityCommand = null;
        qlCompiler = null;
        throwRuntimeExceptions = false;
        eagerLoadGroup = "*";
        readAhead = JDBCReadAheadMetaData.DEFAULT;
        cleanReadAheadOnLoad = false;
        optimisticLocking = null;
        audit = null;
        cmpConfig = new CmpConfig();

    }

    /**
     * Constructs jdbc entity meta data defined in the jdbcApplication and
     * with the data from the entity meta data which is loaded from the
     * ejb-jar.xml file.
     *
     * @param jdbcApplication the application in which this entity is defined
     * @param entity          the entity meta data for this entity that is loaded
     *                        from the ejb-jar.xml file
     */
    public JDBCEntityMetaData(JDBCApplicationMetaData jdbcApplication, EntityBeanMetaData entity) {
        this.jdbcApplication = jdbcApplication;
        entityName = entity.getEjbName();
        listCacheMax = 1000;
        fetchSize = 0;

        final ClassLoader classLoader = jdbcApplication.getClassLoader();
        try {
            entityClass = classLoader.loadClass(entity.getEjbClass());
        } catch (ClassNotFoundException e) {
            throw MESSAGES.failedToLoadEntityClass(e);
        }
        try {
            primaryKeyClass = classLoader.loadClass(entity.getPrimKeyClass());
        } catch (ClassNotFoundException e) {
            throw MESSAGES.failedToLoadPkClass(e);
        }

        isCMP1x = entity.isCMP1x();
        if (isCMP1x) {
            abstractSchemaName = (entity.getAbstractSchemaName() == null ? entityName : entity.getAbstractSchemaName());
        } else {
            abstractSchemaName = entity.getAbstractSchemaName();
        }

        primaryKeyFieldName = entity.getPrimKeyField();

        String home = entity.getHome();
        if (home != null) {
            try {
                homeClass = classLoader.loadClass(home);
            } catch (ClassNotFoundException e) {
                throw MESSAGES.failedToLoadHomeClass(e);
            }
            try {
                remoteClass = classLoader.loadClass(entity.getRemote());
            } catch (ClassNotFoundException e) {
                throw MESSAGES.failedToLoadRemoteClass(e);
            }
        } else {
            homeClass = null;
            remoteClass = null;
        }

        String localHome = entity.getLocalHome();
        if (localHome != null) {
            try {
                localHomeClass = classLoader.loadClass(localHome);
            } catch (ClassNotFoundException e) {
                throw MESSAGES.failedToLoadLocalHomeClass(e);
            }
            try {
                localClass = classLoader.loadClass(entity.getLocal());
            } catch (ClassNotFoundException e) {
                throw MESSAGES.failedToLoadLocalClass(e);
            }
        } else {
            // we must have a home or local home
            if (home == null) {
                throw MESSAGES.entityMustHaveHome(entityName);
            }

            localHomeClass = null;
            localClass = null;
        }

        // we replace the . by _ because some dbs die on it...
        // the table name may be overridden in importXml(jbosscmp-jdbc.xml)
        tableName = entityName.replace('.', '_');

        // Warn: readTimeOut should be setup before cmp fields are created
        // otherwise readTimeOut in cmp fields will be set to 0 by default
        dataSourceName = null;
        dataSourceMappingName = null;
        createTable = false;
        createTableIfNotExistsSupported = false;
        removeTable = false;
        alterTable = false;
        rowLocking = false;
        primaryKeyConstraint = false;
        readOnly = false;
        readTimeOut = -1;
        qlCompiler = null;
        throwRuntimeExceptions = false;

        // build the metadata for the cmp fields now in case there is
        // no jbosscmp-jdbc.xml

        if (entity.getCmpFields() != null) for (CMPFieldMetaData cmpFieldMetaData : entity.getCmpFields()) {
            JDBCCMPFieldMetaData cmpField = new JDBCCMPFieldMetaData(this, cmpFieldMetaData.getFieldName());
            cmpFields.add(cmpField);
            cmpFieldsByName.put(cmpFieldMetaData.getFieldName(), cmpField);
        }

        // AL: add unknown primary key if primaryKeyClass is Object
        // AL: this is set up only in this constructor
        // AL: because, AFAIK, others are called with default value
        // AL: produced by this one
        if (primaryKeyClass == java.lang.Object.class) {
            JDBCCMPFieldMetaData upkField = new JDBCCMPFieldMetaData(this);
            cmpFields.add(upkField);
            cmpFieldsByName.put(upkField.getFieldName(), upkField);
        }

        eagerLoadGroup = "*";

        if (entity.getQueries() != null) for (QueryMetaData queryData : entity.getQueries()) {
            queries.putAll(queryFactory.createJDBCQueryMetaData(queryData));
        }

        readAhead = JDBCReadAheadMetaData.DEFAULT;
        cleanReadAheadOnLoad = false;
        entityCommand = null;
        optimisticLocking = null;
        audit = null;
        cmpConfig = new CmpConfig();
    }

    public JDBCEntityMetaData(JDBCApplicationMetaData jdbcApplication, JDBCEntityMetaData defaultValues) {
        this.jdbcApplication = jdbcApplication;
        entityName = defaultValues.entityName;
        entityClass = defaultValues.entityClass;
        primaryKeyClass = defaultValues.primaryKeyClass;
        isCMP1x = defaultValues.isCMP1x;
        primaryKeyFieldName = defaultValues.primaryKeyFieldName;
        homeClass = defaultValues.homeClass;
        remoteClass = defaultValues.remoteClass;
        localHomeClass = defaultValues.localHomeClass;
        localClass = defaultValues.localClass;
        abstractSchemaName = defaultValues.abstractSchemaName;
        dataSourceName = defaultValues.dataSourceName;
        dataSourceMappingName = defaultValues.dataSourceMappingName;
        tableName = defaultValues.tableName;
        createTable = defaultValues.createTable;
        createTableIfNotExistsSupported = defaultValues.createTableIfNotExistsSupported;
        removeTable = defaultValues.removeTable;
        alterTable = defaultValues.alterTable;
        tablePostCreateCmd.addAll(defaultValues.tablePostCreateCmd);
        readOnly = defaultValues.readOnly;
        readTimeOut = defaultValues.readTimeOut;
        rowLocking = defaultValues.rowLocking;
        primaryKeyConstraint = defaultValues.primaryKeyConstraint;
        listCacheMax = defaultValues.listCacheMax;
        fetchSize = defaultValues.fetchSize;
        entityCommand = defaultValues.entityCommand;
        qlCompiler = defaultValues.qlCompiler;
        throwRuntimeExceptions = defaultValues.throwRuntimeExceptions;

        for (JDBCCMPFieldMetaData cmpField : defaultValues.cmpFields) {
            JDBCCMPFieldMetaData newCmpField = new JDBCCMPFieldMetaData(this, cmpField);
            cmpFields.add(cmpField);
            cmpFieldsByName.put(cmpField.getFieldName(), newCmpField);
        }

        loadGroups.putAll(defaultValues.loadGroups);
        eagerLoadGroup = defaultValues.eagerLoadGroup;
        readAhead = defaultValues.readAhead;
        cleanReadAheadOnLoad = defaultValues.cleanReadAheadOnLoad;
        optimisticLocking = defaultValues.optimisticLocking;
        audit = defaultValues.audit;


        for (JDBCQueryMetaData query : defaultValues.queries.values()) {
            queries.put(query.getMethod(), queryFactory.createJDBCQueryMetaData(query, readAhead, qlCompiler));
        }
        cmpConfig = new CmpConfig();
    }

    public JDBCEntityMetaData(JDBCApplicationMetaData jdbcApplication, ParsedEntity parsed, JDBCEntityMetaData defaultValues) {
        // store passed in application... application in defaultValues may
        // be different because jdbcApplication is immutable
        this.jdbcApplication = jdbcApplication;

        // set default values
        entityName = defaultValues.getName();
        entityClass = defaultValues.getEntityClass();
        primaryKeyClass = defaultValues.getPrimaryKeyClass();
        isCMP1x = defaultValues.isCMP1x;
        primaryKeyFieldName = defaultValues.getPrimaryKeyFieldName();
        homeClass = defaultValues.getHomeClass();
        remoteClass = defaultValues.getRemoteClass();
        localHomeClass = defaultValues.getLocalHomeClass();
        localClass = defaultValues.getLocalClass();

        if (isCMP1x) {
            abstractSchemaName = (defaultValues.getAbstractSchemaName() == null ?
                    entityName : defaultValues.getAbstractSchemaName());
        } else {
            abstractSchemaName = defaultValues.getAbstractSchemaName();
        }

        // datasource name
        if (parsed.getDataSourceName() != null) {
            dataSourceName = parsed.getDataSourceName();
        } else {
            dataSourceName = defaultValues.getDataSourceName();
        }

        // get the datasource mapping for this datasource (optional, but always
        // set in standardjbosscmp-jdbc.xml)
        if (parsed.getDataSourceMappingName() != null) {
            dataSourceMappingName = parsed.getDataSourceMappingName();
        } else {
            dataSourceMappingName = defaultValues.dataSourceMappingName;
        }

        // get table name
        if (parsed.getTableName() != null) {
            tableName = parsed.getTableName();
        } else {
            tableName = defaultValues.getDefaultTableName();
        }

        // create table?  If not provided, keep default.
        if (parsed.getCreateTable() != null) {
            createTable = parsed.getCreateTable();
        } else {
            createTable = defaultValues.getCreateTable();
        }

        if (parsed.getCreateTableIfNotExistsSupported() != null) {
            createTableIfNotExistsSupported = parsed.getCreateTableIfNotExistsSupported();
        } else {
            createTableIfNotExistsSupported = defaultValues.getCreateTableIfNotExistsSupported();
        }

        // remove table?  If not provided, keep default.
        if (parsed.getRemoveTable() != null) {
            removeTable = parsed.getRemoveTable();
        } else {
            removeTable = defaultValues.getRemoveTable();
        }

        // alter table?  If not provided, keep default.
        if (parsed.getAlterTable() != null) {
            alterTable = parsed.getAlterTable();
        } else {
            alterTable = defaultValues.getAlterTable();
        }

        // get the SQL command to execute after table creation
        if (!parsed.getTablePostCreateCmd().isEmpty()) {
            tablePostCreateCmd.addAll(parsed.getTablePostCreateCmd());
        } else {
            tablePostCreateCmd.addAll(defaultValues.getDefaultTablePostCreateCmd());
        }

        // read-only
        if (parsed.getReadOnly() != null) {
            readOnly = parsed.getReadOnly();
        } else {
            readOnly = defaultValues.isReadOnly();
        }

        // read-time-out
        if (parsed.getReadTimeOut() != null) {
            readTimeOut = parsed.getReadTimeOut();
        } else {
            readTimeOut = defaultValues.getReadTimeOut();
        }

        if (parsed.getRowLocking() != null) {
            rowLocking = !isReadOnly() && parsed.getRowLocking();
        } else {
            rowLocking = defaultValues.hasRowLocking();
        }

        // primary key constraint?  If not provided, keep default.
        if (parsed.getPrimaryKeyConstraint() != null) {
            primaryKeyConstraint = parsed.getPrimaryKeyConstraint();
        } else {
            primaryKeyConstraint = defaultValues.hasPrimaryKeyConstraint();
        }

        // list-cache-max
        Integer listCacheMaxInt = parsed.getListCacheMax();
        if (listCacheMaxInt != null) {
            listCacheMax = listCacheMaxInt;
            if (listCacheMax < 0) {
                throw MESSAGES.negativeListCacheMax(listCacheMax);
            }
        } else {
            listCacheMax = defaultValues.getListCacheMax();
        }

        // fetch-size
        Integer fetchSizeInt = parsed.getFetchSize();
        if (fetchSizeInt != null) {
            fetchSize = fetchSizeInt;
            if (fetchSize < 0) {
                throw MESSAGES.negativeFetchSize(fetchSize);
            }
        } else {
            fetchSize = defaultValues.getFetchSize();
        }

        if (parsed.getQlCompiler() == null) {
            qlCompiler = defaultValues.qlCompiler;
        } else {
            qlCompiler = parsed.getQlCompiler();
        }

        // throw runtime exceptions ?  If not provided, keep default.
        if (parsed.getThrowRuntimeExceptions() != null) {
            throwRuntimeExceptions = parsed.getThrowRuntimeExceptions();
        } else {
            throwRuntimeExceptions = defaultValues.getThrowRuntimeExceptions();
        }


        //
        // cmp fields
        //

        // update all existing queries with the new read ahead value
        for (JDBCCMPFieldMetaData cmpField : defaultValues.cmpFields) {
            cmpFields.add(cmpField);
            cmpFieldsByName.put(cmpField.getFieldName(), cmpField);
        }

        // apply new configurations to the cmpfields
        for (ParsedCmpField parsedField : parsed.getCmpFields()) {
            String fieldName = parsedField.getFieldName();
            JDBCCMPFieldMetaData oldCMPField = cmpFieldsByName.get(fieldName);
            if (oldCMPField == null) {
                throw MESSAGES.cmpFieldNotFound(fieldName, entityName);
            }
            JDBCCMPFieldMetaData cmpFieldMetaData = new JDBCCMPFieldMetaData(this, parsedField, oldCMPField);

            // replace the old cmp meta data with the new
            cmpFieldsByName.put(fieldName, cmpFieldMetaData);
            int index = cmpFields.indexOf(oldCMPField);
            cmpFields.remove(oldCMPField);
            cmpFields.add(index, cmpFieldMetaData);
        }

        // unknown primary key field
        if (primaryKeyClass == java.lang.Object.class) {
            if (parsed.getUpkField() != null) {
                // assume now there is only one upk field
                JDBCCMPFieldMetaData oldUpkField = null;
                for (JDBCCMPFieldMetaData cmpField : cmpFields) {
                    if (cmpField.isUnknownPkField()) {
                        oldUpkField = cmpField;
                        break;
                    }
                }

                // IMO, this is a redundant check
                if (oldUpkField == null) {
                    oldUpkField = new JDBCCMPFieldMetaData(this);
                }

                JDBCCMPFieldMetaData upkField = new JDBCCMPFieldMetaData(this, parsed.getUpkField(), oldUpkField);

                // remove old upk field
                cmpFieldsByName.remove(oldUpkField.getFieldName());
                cmpFieldsByName.put(upkField.getFieldName(), upkField);

                int oldUpkFieldInd = cmpFields.indexOf(oldUpkField);
                cmpFields.remove(oldUpkField);
                cmpFields.add(oldUpkFieldInd, upkField);
            }
        }

        // load-loads
        loadGroups.putAll(defaultValues.loadGroups);
        loadGroups.putAll(parsed.getLoadGroups());

        // eager-load
        if (parsed.getEagerLoadGroup() != null) {
            if (!parsed.getEagerLoadGroup().equals("*") && !loadGroups.containsKey(parsed.getEagerLoadGroup())) {
                throw CmpMessages.MESSAGES.eagerLoadGroupNotFound(parsed.getEagerLoadGroup());
            }
            eagerLoadGroup = parsed.getEagerLoadGroup();
        } else {
            eagerLoadGroup = defaultValues.getEagerLoadGroup();
        }

        // lazy-loads
        lazyLoadGroups.addAll(defaultValues.lazyLoadGroups);
        lazyLoadGroups.addAll(parsed.getLazyLoadGroups());

        // read-ahead
        if (parsed.getReadAhead() != null) {
            readAhead = new JDBCReadAheadMetaData(parsed.getReadAhead(), defaultValues.getReadAhead());
        } else {
            readAhead = defaultValues.readAhead;
        }

        if (parsed.getCleanReadAheadOnLoad() != null) {
            cleanReadAheadOnLoad = parsed.getCleanReadAheadOnLoad();
        } else {
            cleanReadAheadOnLoad = defaultValues.cleanReadAheadOnLoad;
        }

        // optimistic locking group
        if (parsed.getOptimisticLocking() != null) {
            optimisticLocking = new JDBCOptimisticLockingMetaData(this, parsed.getOptimisticLocking());
        } else {
            optimisticLocking = defaultValues.getOptimisticLocking();
        }

        // audit
        if (parsed.getAudit() != null) {
            audit = new JDBCAuditMetaData(this, parsed.getAudit());
        } else {
            audit = defaultValues.getAudit();
        }

        // queries

        // update all existing queries with the new read ahead value
        for (JDBCQueryMetaData query : defaultValues.queries.values()) {
            query = queryFactory.createJDBCQueryMetaData(query, readAhead, qlCompiler);
            queries.put(query.getMethod(), query);
        }

        // apply new configurations to the queries
        for (ParsedQuery parsedQuery : parsed.getQueries()) {
            // overrides defaults added above
            for (JDBCQueryMetaData queryMetaData : queryFactory.createJDBCQueryMetaData(parsedQuery)) {
                queries.put(queryMetaData.getMethod(), queryMetaData);
            }
        }

        // get the entity command for this entity
        if (parsed.getEntityCommand() != null) {
            String entityCommandName = parsed.getEntityCommand().getCommandName();
            JDBCEntityCommandMetaData defaultEntityCommand = defaultValues.getEntityCommand();

            if ((defaultEntityCommand == null) || (!entityCommandName.equals(defaultEntityCommand.getCommandName()))) {
                defaultEntityCommand = jdbcApplication.getEntityCommandByName(entityCommandName);
            }

            if (defaultEntityCommand != null) {
                entityCommand = new JDBCEntityCommandMetaData(parsed.getEntityCommand(), defaultEntityCommand);
            } else {
                entityCommand = parsed.getEntityCommand();
            }
        } else {
            entityCommand = defaultValues.getEntityCommand();
        }

        cmpConfig = parsed.getCmpConfig();
    }

    /**
     * Gets the meta data for the application of which this entity is a member.
     *
     * @return the meta data for the application that this entity is a member
     */
    public JDBCApplicationMetaData getJDBCApplication() {
        return jdbcApplication;
    }

    /**
     * Gets the name of the datasource in jndi for this entity
     *
     * @return the name of datasource in jndi
     */
    public String getDataSourceName() {
        return dataSourceName;
    }

    /**
     * Gets the jdbc type mapping for this entity
     *
     * @return the jdbc type mapping for this entity
     */
    public JDBCTypeMappingMetaData getTypeMapping() {
        if (dataSourceMappingName == null) {
            return null;
        }
        final JDBCTypeMappingMetaData typeMapping = jdbcApplication.getTypeMappingByName(dataSourceMappingName);
        if (typeMapping == null) {
            throw MESSAGES.typeMappingNotInitialized(dataSourceName);
        }

        return typeMapping;
    }

    /**
     * Gets the name of this entity. The name come from the ejb-jar.xml file.
     *
     * @return the name of this entity
     */
    public String getName() {
        return entityName;
    }

    /**
     * Gets the abstract schema name of this entity. The name come from
     * the ejb-jar.xml file.
     *
     * @return the abstract schema name of this entity
     */
    public String getAbstractSchemaName() {
        return abstractSchemaName;
    }

    /**
     * Gets the implementation class of this entity
     *
     * @return the implementation class of this entity
     */
    public Class<?> getEntityClass() {
        return entityClass;
    }

    /**
     * Gets the home class of this entity
     *
     * @return the home class of this entity
     */
    public Class<?> getHomeClass() {
        return homeClass;
    }

    /**
     * Gets the remote class of this entity
     *
     * @return the remote class of this entity
     */
    public Class<?> getRemoteClass() {
        return remoteClass;
    }

    /**
     * Gets the local home class of this entity
     *
     * @return the local home class of this entity
     */
    public Class<?> getLocalHomeClass() {
        return localHomeClass;
    }

    /**
     * Gets the local class of this entity
     *
     * @return the local class of this entity
     */
    public Class<?> getLocalClass() {
        return localClass;
    }

    /**
     * Does this entity use CMP version 1.x
     *
     * @return true if this entity used CMP version 1.x; otherwise false
     */
    public boolean isCMP1x() {
        return isCMP1x;
    }

    /**
     * Gets the cmp fields of this entity
     *
     * @return an unmodifiable collection of JDBCCMPFieldMetaData objects
     */
    public List<JDBCCMPFieldMetaData> getCMPFields() {
        return Collections.unmodifiableList(cmpFields);
    }

    /**
     * Gets the name of the eager load group. This name can be used to
     * look up the load group.
     *
     * @return the name of the eager load group
     */
    public String getEagerLoadGroup() {
        return eagerLoadGroup;
    }

    /**
     * Gets the collection of lazy load group names.
     *
     * @return an unmodifiable collection of load group names
     */
    public List<String> getLazyLoadGroups() {
        return Collections.unmodifiableList(lazyLoadGroups);
    }

    /**
     * Gets the map from load group name to a List of field names, which
     * forms a logical load group.
     *
     * @return an unmodifiable map of load groups (Lists) by group name.
     */
    public Map<String, List<String>> getLoadGroups() {
        return Collections.unmodifiableMap(loadGroups);
    }

    /**
     * Gets the load group with the specified name.
     *
     * @return the load group with the specified name
     */
    public List<String> getLoadGroup(String name) {
        List<String> group = loadGroups.get(name);
        if (group == null) {
            throw MESSAGES.unknownLoadGroup(name);
        }
        return group;
    }

    /**
     * Returns optimistic locking metadata
     */
    public JDBCOptimisticLockingMetaData getOptimisticLocking() {
        return optimisticLocking;
    }

    /**
     * Returns audit metadata
     */
    public JDBCAuditMetaData getAudit() {
        return audit;
    }

    /**
     * Gets the cmp field with the specified name
     *
     * @param name the name of the desired field
     * @return the cmp field with the specified name or null if not found
     */
    public JDBCCMPFieldMetaData getCMPFieldByName(String name) {
        return cmpFieldsByName.get(name);
    }

    /**
     * Gets the name of the table to which this entity is persisted
     *
     * @return the name of the table to which this entity is persisted
     */
    public String getDefaultTableName() {
        return tableName;
    }

    /**
     * Gets the flag used to determine if the store manager should attempt to
     * create database table when the entity is deployed.
     *
     * @return true if the store manager should attempt to create the table
     */
    public boolean getCreateTable() {
        return createTable;
    }

    /**
     * Gets the flag, which indicates that CREATE TABLE IF NOT EXISTS sql dialect is supported
     * @return
     */
    public boolean getCreateTableIfNotExistsSupported() {
        return createTableIfNotExistsSupported;
    }

    /**
     * Gets the flag used to determine if the store manager should attempt to
     * remove database table when the entity is undeployed.
     *
     * @return true if the store manager should attempt to remove the table
     */
    public boolean getRemoveTable() {
        return removeTable;
    }

    /**
     * Gets the flag used to determine if the store manager should attempt to
     * alter table when the entity is deployed.
     */
    public boolean getAlterTable() {
        return alterTable;
    }

    /**
     * Get the (user-defined) SQL commands that should be issued after table
     * creation
     *
     * @return the SQL command to issue to the DB-server
     */
    public List<String> getDefaultTablePostCreateCmd() {
        return tablePostCreateCmd;
    }

    /**
     * Gets the flag used to determine if the store manager should add a
     * primary key constraint when creating the table
     *
     * @return true if the store manager should add a primary key constraint to
     *         the create table sql statement
     */
    public boolean hasPrimaryKeyConstraint() {
        return primaryKeyConstraint;
    }

    /**
     * Gets the flag used to determine if the store manager should do row locking
     * when loading entity beans
     *
     * @return true if the store manager should add a row locking
     *         clause when selecting data from the table
     */
    public boolean hasRowLocking() {
        return rowLocking;
    }

    /**
     * The maximum number of query result lists that will be tracked.
     */
    public int getListCacheMax() {
        return listCacheMax;
    }

    /**
     * The number of rows that the database driver should get in a single
     * trip to the database.
     */
    public int getFetchSize() {
        return fetchSize;
    }

    /**
     * Gets the queries defined on this entity
     *
     * @return an unmodifiable collection of JDBCQueryMetaData objects
     */
    public Collection<JDBCQueryMetaData> getQueries() {
        return Collections.unmodifiableCollection(queries.values());
    }

    /**
     * @param method finder method name.
     * @return corresponding query metadata or null.
     */
    public JDBCQueryMetaData getQueryMetaDataForMethod(Method method) {
        return (JDBCQueryMetaData) queries.get(method);
    }

    /**
     * Get the relationship roles of this entity.
     * Items are instance of JDBCRelationshipRoleMetaData.
     *
     * @return an unmodifiable collection of the relationship roles defined
     *         for this entity
     */
    public Collection<JDBCRelationshipRoleMetaData> getRelationshipRoles() {
        return jdbcApplication.getRolesForEntity(entityName);
    }

    /**
     * Gets the primary key class for this entity
     *
     * @return the primary key class for this entity
     */
    public Class<?> getPrimaryKeyClass() {
        return primaryKeyClass;
    }

    /**
     * Gets the entity command metadata
     *
     * @return the entity command metadata
     */
    public JDBCEntityCommandMetaData getEntityCommand() {
        return entityCommand;
    }

    /**
     * Is this entity read only? A readonly entity will never be stored into
     * the database.
     *
     * @return true if this entity is read only
     */
    public boolean isReadOnly() {
        return readOnly;
    }

    /**
     * How long is a read of this entity valid. This property should only be
     * used on read only entities, and determines how long the data read from
     * the database is valid. When the read times out it should be reread from
     * the database. If the value is -1 and the entity is not using commit
     * option a, the read is only valid for the length of the transaction in
     * which it was loaded.
     *
     * @return the length of time that a read is valid or -1 if the read is only
     *         valid for the length of the transaction
     */
    public int getReadTimeOut() {
        return readTimeOut != null ? readTimeOut : -1;
    }

    /**
     * Gets the name of the primary key field of this entity or null if
     * the primary key is multivalued
     *
     * @return the name of the primary key field of this entity or null
     *         if the primary key is multivalued
     */
    public String getPrimaryKeyFieldName() {
        return primaryKeyFieldName;
    }


    /**
     * Gets the read ahead meta data for this entity.
     *
     * @return the read ahead meta data for this entity.
     */
    public JDBCReadAheadMetaData getReadAhead() {
        return readAhead;
    }

    public Class<?> getQlCompiler() {
        return qlCompiler;
    }

    /**
     * Is the throw-runtime-exceptions meta data for this entity is true.
     *
     * @return the throw-runtime-exceptions meta data for this entity.
     */
    public boolean isThrowRuntimeExceptions() {
        return throwRuntimeExceptions;
    }

    /**
     * Gets the throw-runtime-exceptions meta data for this entity.
     *
     * @return the throw-runtime-exceptions meta data for this entity.
     */
    public boolean getThrowRuntimeExceptions() {
        return throwRuntimeExceptions;
    }


    public boolean isCleanReadAheadOnLoad() {
        return cleanReadAheadOnLoad;
    }

    public CmpConfig getCmpConfig() {
        return cmpConfig;
    }

    /**
     * Compares this JDBCEntityMetaData against the specified object. Returns
     * true if the objects are the same. Two JDBCEntityMetaData are the same
     * if they both have the same name and are defined in the same application.
     *
     * @param o the reference object with which to compare
     * @return true if this object is the same as the object argument;
     *         false otherwise
     */
    public boolean equals(Object o) {
        if (o instanceof JDBCEntityMetaData) {
            JDBCEntityMetaData entity = (JDBCEntityMetaData) o;
            return entityName.equals(entity.entityName) &&
                    jdbcApplication.equals(entity.jdbcApplication);
        }
        return false;
    }

    /**
     * Returns a hashcode for this JDBCEntityMetaData. The hashcode is computed
     * based on the hashCode of the declaring application and the hashCode of
     * the entityName
     *
     * @return a hash code value for this object
     */
    public int hashCode() {
        int result = 17;
        result = 37 * result + jdbcApplication.hashCode();
        result = 37 * result + entityName.hashCode();
        return result;
    }

    /**
     * Returns a string describing this JDBCEntityMetaData. The exact details
     * of the representation are unspecified and subject to change, but the
     * following may be regarded as typical:
     * <p/>
     * "[JDBCEntityMetaData: entityName=UserEJB]"
     *
     * @return a string representation of the object
     */
    public String toString() {
        return "[JDBCEntityMetaData : entityName=" + entityName + "]";
    }
}
