com.netspective.axiom.schema.table.BasicTable.java Source code

Java tutorial

Introduction

Here is the source code for com.netspective.axiom.schema.table.BasicTable.java

Source

/*
 * Copyright (c) 2000-2004 Netspective Communications LLC. All rights reserved.
 *
 * Netspective Communications LLC ("Netspective") permits redistribution, modification and use of this file in source
 * and binary form ("The Software") under the Netspective Source License ("NSL" or "The License"). The following
 * conditions are provided as a summary of the NSL but the NSL remains the canonical license and must be accepted
 * before using The Software. Any use of The Software indicates agreement with the NSL.
 *
 * 1. Each copy or derived work of The Software must preserve the copyright notice and this notice unmodified.
 *
 * 2. Redistribution of The Software is allowed in object code form only (as Java .class files or a .jar file
 *    containing the .class files) and only as part of an application that uses The Software as part of its primary
 *    functionality. No distribution of the package is allowed as part of a software development kit, other library,
 *    or development tool without written consent of Netspective. Any modified form of The Software is bound by these
 *    same restrictions.
 *
 * 3. Redistributions of The Software in any form must include an unmodified copy of The License, normally in a plain
 *    ASCII text file unless otherwise agreed to, in writing, by Netspective.
 *
 * 4. The names "Netspective", "Axiom", "Commons", "Junxion", and "Sparx" are trademarks of Netspective and may not be
 *    used to endorse or appear in products derived from The Software without written consent of Netspective.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" WITHOUT A WARRANTY OF ANY KIND. ALL EXPRESS OR IMPLIED REPRESENTATIONS AND
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT,
 * ARE HEREBY DISCLAIMED.
 *
 * NETSPECTIVE AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE OR ANY THIRD PARTY AS A
 * RESULT OF USING OR DISTRIBUTING THE SOFTWARE. IN NO EVENT WILL NETSPECTIVE OR ITS LICENSORS BE LIABLE FOR ANY LOST
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THE SOFTWARE, EVEN
 * IF IT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 */
package com.netspective.axiom.schema.table;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import javax.naming.NamingException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import com.netspective.axiom.ConnectionContext;
import com.netspective.axiom.DatabasePolicy;
import com.netspective.axiom.schema.BasicSchema;
import com.netspective.axiom.schema.Column;
import com.netspective.axiom.schema.ColumnValue;
import com.netspective.axiom.schema.ColumnValues;
import com.netspective.axiom.schema.Columns;
import com.netspective.axiom.schema.ForeignKey;
import com.netspective.axiom.schema.Index;
import com.netspective.axiom.schema.IndexColumns;
import com.netspective.axiom.schema.Indexes;
import com.netspective.axiom.schema.PrimaryKeyColumnValues;
import com.netspective.axiom.schema.PrimaryKeyColumns;
import com.netspective.axiom.schema.Row;
import com.netspective.axiom.schema.RowDeleteType;
import com.netspective.axiom.schema.Rows;
import com.netspective.axiom.schema.Schema;
import com.netspective.axiom.schema.Table;
import com.netspective.axiom.schema.TableHierarchyReference;
import com.netspective.axiom.schema.TableRowTrigger;
import com.netspective.axiom.schema.Tables;
import com.netspective.axiom.schema.column.BasicColumn;
import com.netspective.axiom.schema.column.ColumnsCollection;
import com.netspective.axiom.schema.column.PrimaryKeyColumnsCollection;
import com.netspective.axiom.schema.constraint.ParentForeignKey;
import com.netspective.axiom.sql.Queries;
import com.netspective.axiom.sql.Query;
import com.netspective.axiom.sql.QueryExecutionLog;
import com.netspective.axiom.sql.QueryResultSet;
import com.netspective.axiom.sql.dynamic.QueryDefnCondition;
import com.netspective.axiom.sql.dynamic.QueryDefnConditionConnectorEnumeratedAttribute;
import com.netspective.axiom.sql.dynamic.QueryDefnField;
import com.netspective.axiom.sql.dynamic.QueryDefnFieldReference;
import com.netspective.axiom.sql.dynamic.QueryDefnJoin;
import com.netspective.axiom.sql.dynamic.QueryDefnSelect;
import com.netspective.axiom.sql.dynamic.SqlComparisonEnumeratedAttribute;
import com.netspective.axiom.sql.dynamic.exception.QueryDefinitionException;
import com.netspective.axiom.sql.dynamic.exception.QueryDefnFieldNotFoundException;
import com.netspective.axiom.sql.dynamic.exception.QueryDefnSqlComparisonNotFoundException;
import com.netspective.commons.io.InputSourceLocator;
import com.netspective.commons.text.TextUtils;
import com.netspective.commons.validate.ValidationUtils;
import com.netspective.commons.xdm.XmlDataModelSchema;
import com.netspective.commons.xml.template.Template;
import com.netspective.commons.xml.template.TemplateConsumer;
import com.netspective.commons.xml.template.TemplateConsumerDefn;
import com.netspective.commons.xml.template.TemplateElement;
import com.netspective.commons.xml.template.TemplateNode;
import com.netspective.commons.xml.template.TemplateProducer;
import com.netspective.commons.xml.template.TemplateProducerParent;
import com.netspective.commons.xml.template.TemplateProducers;
import com.netspective.commons.xml.template.TemplateText;

public class BasicTable implements Table, TemplateProducerParent, TemplateConsumer {
    private static final Log log = LogFactory.getLog(BasicTable.class);
    public static final XmlDataModelSchema.Options XML_DATA_MODEL_SCHEMA_OPTIONS = new XmlDataModelSchema.Options();
    public static final String ATTRNAME_TYPE = "type";
    public static final String[] ATTRNAMES_SET_BEFORE_CONSUMING = new String[] { "name", "abbrev" };

    static {
        XML_DATA_MODEL_SCHEMA_OPTIONS.setIgnorePcData(true);
        XML_DATA_MODEL_SCHEMA_OPTIONS.addIgnoreNestedElements(new String[] { "row" });
    }

    protected class TableTypeTemplateConsumerDefn extends TemplateConsumerDefn {
        public TableTypeTemplateConsumerDefn() {
            super(getSchema().getTableTypesTemplatesNameSpaceId(), ATTRNAME_TYPE, ATTRNAMES_SET_BEFORE_CONSUMING);
        }
    }

    protected class TablePresentationTemplate extends TemplateProducer {
        public TablePresentationTemplate() {
            super(getSchema().getPresentationTemplatesNameSpaceId(), BasicSchema.TEMPLATEELEMNAME_PRESENTATION,
                    null, null, false, false);
        }

        public String getTemplateName(String url, String localName, String qName, Attributes attributes)
                throws SAXException {
            return getName();
        }
    }

    private InputSourceLocator inputSourceLocator;
    private Schema schema;
    private Column parentColumn;
    private String name;
    private boolean quoteNameInSql;
    private String abbrev;
    private String caption;
    private String xmlNodeName;
    private String javaClassName;
    private String javaCollectionName;
    private List tableTypesConsumed = new ArrayList();
    private String description;
    private Tables parentTables = new TablesCollection();
    private TableHierarchyReference hierarchyRef;
    private Columns columns = new ColumnsCollection();
    private Tables childTables = new TablesCollection();
    private PrimaryKeyColumns primaryKeyColumns;
    private Columns parentRefColumns;
    private QueryExecutionLog execLog = new QueryExecutionLog();
    private TableQueryDefinition queryDefn = null;
    private QueryDefnSelect selectByPrimaryKey;
    private QueryDefnSelect selectCount;
    private String primaryKeyWhereClauseExpr;
    private QueryDefnSelect selectByParentKey;
    private RowDeleteType rowDeleteType = new RowDeleteType(RowDeleteType.PHYSICAL);
    private String logicalDeleteUpdateSqlSetClauseFormat;
    private Rows staticData;
    private Indexes indexes = new IndexesCollection(true);
    private TablePresentationTemplate presentation;
    private TemplateProducers templateProducers;
    private TemplateConsumerDefn templateConsumer;
    private TableRowTrigger[] triggers = new TableRowTrigger[0];
    private TableSqlDataDefns sqlDataDefns = new TableSqlDataDefns(this);

    static public String translateTableNameForMapKey(String name) {
        return name != null ? name.toUpperCase() : null;
    }

    public InputSourceLocator getInputSourceLocator() {
        return inputSourceLocator;
    }

    public void setInputSourceLocator(InputSourceLocator inputSourceLocator) {
        this.inputSourceLocator = inputSourceLocator;
    }

    public TemplateConsumerDefn getTemplateConsumerDefn() {
        if (templateConsumer == null)
            templateConsumer = new TableTypeTemplateConsumerDefn();
        return templateConsumer;
    }

    public void registerTemplateConsumption(Template template) {
        tableTypesConsumed.add(template.getTemplateName());
    }

    public List getTableTypes() {
        return tableTypesConsumed;
    }

    public TemplateProducer getPresentation() {
        if (presentation == null)
            presentation = new TablePresentationTemplate();
        return presentation;
    }

    public TemplateProducers getTemplateProducers() {
        if (templateProducers == null) {
            templateProducers = new TemplateProducers();
            templateProducers.add(getPresentation());
        }
        return templateProducers;
    }

    public BasicTable(Schema schema) {
        setSchema(schema);
    }

    public BasicTable(Column parentColumn) {
        setParentColumn(parentColumn);
    }

    public void finishConstruction() {
        columns.finishConstruction();
        if (hierarchyRef != null) {
            Table parentTable = schema.getTables().getByName(hierarchyRef.getParent());
            if (parentTable == null)
                throw new RuntimeException("Unable to find table '" + hierarchyRef.getParent() + "' for '"
                        + getName() + "' parent hierarchy");
            parentTable.registerChildTable(this);
        }
    }

    public Queries getContainer() {
        return getSchema().getSqlManager().getQueries(); // we're storing queries in the global queries list
    }

    public String getNameSpaceId() {
        return getSchema().getName() + "." + getName(); // our namespace is the schema.tableName
    }

    public void setContainer(Queries container) {
        // we're storing queries in the global queries list so we ignore this
    }

    public void setNameSpaceId(String identifier) {
        // our namespace is the schema.tableName so we ignore this
    }

    public boolean isApplicationTable() {
        return true;
    }

    public Schema getSchema() {
        return schema;
    }

    public void setSchema(Schema value) {
        schema = value;
    }

    public Column getParentColumn() {
        return parentColumn;
    }

    public void setParentColumn(Column parentColumn) {
        this.parentColumn = parentColumn;
        setSchema(parentColumn.getSchema());
    }

    public String getName() {
        return name;
    }

    public String getSqlName() {
        return quoteNameInSql ? "\"" + name + "\"" : name;
    }

    public String getNameForMapKey() {
        return name != null ? name.toUpperCase() : null;
    }

    public boolean isQuoteNameInSql() {
        return quoteNameInSql;
    }

    public void setQuoteNameInSql(boolean quoteNameInSql) {
        this.quoteNameInSql = quoteNameInSql;
    }

    public void setName(String value) {
        name = value;
    }

    public String getXmlNodeName() {
        return xmlNodeName == null ? TextUtils.getInstance().xmlTextToNodeName(getName()) : xmlNodeName;
    }

    public void setXmlNodeName(String value) {
        xmlNodeName = value;
    }

    public String getAbbrev() {
        return abbrev != null ? abbrev : getName();
    }

    public void setAbbrev(String abbrev) {
        this.abbrev = abbrev;
    }

    public String getCaption() {
        return caption == null ? TextUtils.getInstance().sqlIdentifierToText(getName(), true) : caption;
    }

    public void setCaption(String caption) {
        this.caption = caption;
    }

    public String getJavaClassName() {
        return javaClassName == null ? TextUtils.getInstance().xmlTextToJavaIdentifier(getName(), true)
                : javaClassName;
    }

    public String getJavaClassName(String defaultJavaClassName) {
        return javaClassName == null ? defaultJavaClassName : javaClassName;
    }

    public void setJavaClassName(String javaClassName) {
        this.javaClassName = javaClassName;
    }

    public String getJavaCollectionName() {
        return javaCollectionName == null ? TextUtils.getInstance().xmlTextToJavaIdentifier(getName(), false) + "s"
                : javaCollectionName;
    }

    public String getJavaCollectionName(String defaultJavaClassName) {
        return javaCollectionName == null ? defaultJavaClassName : javaCollectionName;
    }

    public void setJavaCollectionName(String javaCollectionName) {
        this.javaCollectionName = javaCollectionName;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String value) {
        description = value;
    }

    public PrimaryKeyColumns getPrimaryKeyColumns() {
        if (primaryKeyColumns == null) {
            primaryKeyColumns = new PrimaryKeyColumnsCollection();
            for (int i = 0; i < columns.size(); i++) {
                Column column = columns.get(i);
                if (column.isPrimaryKey())
                    primaryKeyColumns.add(column);
            }
        }
        return primaryKeyColumns;
    }

    public Columns getForeignKeyColumns(int fkeyType) {
        Columns result = new ColumnsCollection();
        for (int i = 0; i < columns.size(); i++) {
            Column column = columns.get(i);
            ForeignKey fkey = column.getForeignKey();
            if (fkey != null && fkey.getType() == fkeyType)
                result.add(column);
        }
        return result;
    }

    public Columns getForeignKeyColumns() {
        Columns result = new ColumnsCollection();
        for (int i = 0; i < columns.size(); i++) {
            Column column = columns.get(i);
            ForeignKey fkey = column.getForeignKey();
            if (fkey != null)
                result.add(column);
        }
        return result;
    }

    public Columns getParentRefColumns() {
        if (parentRefColumns == null)
            parentRefColumns = getForeignKeyColumns(ForeignKey.FKEYTYPE_PARENT);

        return parentRefColumns;
    }

    public Column createColumn() {
        return new BasicColumn(this);
    }

    public Column createColumn(Class cls) throws NoSuchMethodException, InstantiationException,
            IllegalAccessException, InvocationTargetException {
        if (Column.class.isAssignableFrom(cls)) {
            Constructor c = cls.getConstructor(new Class[] { Table.class });
            return (Column) c.newInstance(new Object[] { this });
        } else
            throw new RuntimeException("Don't know what to do with with class: " + cls);
    }

    public Tables getChildTables() {
        return childTables;
    }

    public Columns getColumns() {
        return columns;
    }

    public void addColumn(Column column) {
        // columns that contain <composite> don't want to be added since they have children that have been added
        if (column.isAllowAddToTable()) {
            int indexInRow = columns.add(column);
            column.setIndexInRow(indexInRow);
        }
    }

    public Rows getData() {
        return staticData;
    }

    public Rows createData() {
        if (staticData == null)
            staticData = createRows();
        return staticData;
    }

    public void addData(Rows data) {
        // staticData is already present, do nothing
    }

    public boolean isChildTable() {
        return parentTables.size() > 0;
    }

    public Tables getParentTables() {
        return parentTables;
    }

    public boolean isParentTable() {
        return childTables != null && childTables.size() > 0;
    }

    public void registerChildTable(Table table) {
        if (childTables == null)
            childTables = new TablesCollection();
        childTables.add(table);
        table.getParentTables().add(table);
    }

    public void removeChildTable(Table table) {
        if (childTables != null)
            childTables.remove(table);
    }

    public TableHierarchyReference createHierarchy() {
        if (hierarchyRef == null)
            hierarchyRef = new BasicTableHierarchyReference();
        return hierarchyRef;
    }

    public TableHierarchyReference getHierarchy() {
        return hierarchyRef;
    }

    public void addHierarchy(TableHierarchyReference hierarchy) {
        // do nothing
    }

    public Row createRow() {
        return new BasicRow(this);
    }

    public Rows createRows() {
        return new BasicRows(this);
    }

    public Row createRow(ParentForeignKey pfKey, Row parentRow) {
        Row childRow = createRow();
        pfKey.fillChildValuesFromParentConnector(childRow.getColumnValues(), parentRow.getColumnValues());
        return childRow;
    }

    /* ------------------------------------------------------------------------------------------------------------- */

    public TableSqlDataDefns getSqlDataDefns() {
        return sqlDataDefns;
    }

    public TableSqlDataDefns createSqlDdl() {
        return sqlDataDefns;
    }

    public void addSqlDdl(TableSqlDataDefns sqlDataDefn) {
        // do nothing -- we have the instance already created, but the XML data model will call this anyway
    }

    /* ------------------------------------------------------------------------------------------------------------- */

    public void refreshData(ConnectionContext cc, Row row) throws NamingException, SQLException {
        getRowByPrimaryKeys(cc, row.getPrimaryKeyValues(), row);
    }

    public boolean dataChangedInStorage(ConnectionContext cc, Row row) throws NamingException, SQLException {
        Row compareTo = getRowByPrimaryKeys(cc, row.getPrimaryKeyValues(), null);
        return !row.equals(compareTo);
    }

    public Row getRowByPrimaryKeys(ConnectionContext cc, PrimaryKeyColumnValues values, Row row)
            throws NamingException, SQLException {
        return getRowByPrimaryKeys(cc, values.getValuesForSqlBindParams(), row);
    }

    public Row getRowByPrimaryKeys(ConnectionContext cc, Object[] pkValues, Row row)
            throws NamingException, SQLException {
        QueryDefnSelect pkSelect = getAccessorByPrimaryKeyEquality();

        Row resultRow = row;
        QueryResultSet qrs = pkSelect.execute(cc, pkValues, false);
        if (qrs != null) {
            ResultSet rs = qrs.getResultSet();
            if (rs.next()) {
                if (resultRow == null)
                    resultRow = createRow();
                resultRow.getColumnValues().populateValues(rs, ColumnValues.RESULTSETROWNUM_SINGLEROW);
            }
            qrs.close(false);
        }

        return resultRow;
    }

    public Row getRowByAccessor(ConnectionContext cc, QueryDefnSelect accessor, Object[] bindValues, Row row)
            throws NamingException, SQLException {
        Row resultRow = row;
        QueryResultSet qrs = accessor.execute(cc, bindValues, false);
        if (qrs != null) {
            ResultSet rs = qrs.getResultSet();
            if (rs.next()) {
                if (resultRow == null)
                    resultRow = createRow();
                resultRow.getColumnValues().populateValues(rs, ColumnValues.RESULTSETROWNUM_SINGLEROW);
            }
            qrs.close(false);
        }
        return resultRow;
    }

    public Rows getRowsByAccessor(ConnectionContext cc, QueryDefnSelect accessor, Object[] bindValues)
            throws NamingException, SQLException {
        Rows resultRows = createRows();
        QueryResultSet qrs = accessor.execute(cc, bindValues, false);
        if (qrs != null) {
            ResultSet rs = qrs.getResultSet();
            while (rs.next()) {
                Row resultRow = createRow();
                resultRow.getColumnValues().populateValues(rs, ColumnValues.RESULTSETROWNUM_SINGLEROW);
                resultRows.addRow(resultRow);
            }
            qrs.close(false);
        }
        return resultRows;
    }

    public Rows getRowsByWhereCond(ConnectionContext cc, String whereCond, Object[] bindValues)
            throws NamingException, SQLException {
        Rows resultRows = createRows();

        StringBuffer findRecsToDeleteSql = new StringBuffer("select ");
        Columns columns = getColumns();
        for (int i = 0; i < columns.size(); i++) {
            if (i > 0)
                findRecsToDeleteSql.append(", ");
            findRecsToDeleteSql.append(columns.get(i).getName());
        }
        findRecsToDeleteSql.append(" from " + cc.getDatabasePolicy().resolveTableName(this));
        if (whereCond != null) {
            findRecsToDeleteSql.append(" ");
            if (!whereCond.startsWith("where"))
                findRecsToDeleteSql.append("where");
            findRecsToDeleteSql.append(" ");
            findRecsToDeleteSql.append(whereCond);
        }

        PreparedStatement stmt = cc.getConnection().prepareStatement(findRecsToDeleteSql.toString());
        try {
            if (bindValues != null) {
                for (int i = 0; i < bindValues.length; i++)
                    stmt.setObject(i + 1, bindValues[i]);
            }
            ResultSet rs = stmt.executeQuery();
            try {
                while (rs.next()) {
                    Row resultRow = createRow();
                    resultRow.getColumnValues().populateValues(rs, ColumnValues.RESULTSETROWNUM_SINGLEROW);
                    resultRows.addRow(resultRow);
                }
            } finally {
                rs.close();
            }
        } finally {
            stmt.close();
        }

        return resultRows;
    }

    public long getCount(ConnectionContext cc, String whereCond, Object[] bindValues)
            throws NamingException, SQLException {
        StringBuffer sql = new StringBuffer("select count(*) from ");
        sql.append(cc.getDatabasePolicy().resolveTableName(this));
        if (whereCond != null) {
            sql.append(" ");
            if (!whereCond.startsWith("where"))
                sql.append("where");
            sql.append(" ");
            sql.append(whereCond);
        }

        PreparedStatement stmt = cc.getConnection().prepareStatement(sql.toString());
        try {
            if (bindValues != null) {
                for (int i = 0; i < bindValues.length; i++)
                    stmt.setObject(i + 1, bindValues[i]);
            }
            ResultSet rs = stmt.executeQuery();
            try {
                if (rs.next())
                    return rs.getLong(1);
                else
                    return 0;
            } finally {
                rs.close();
            }
        } finally {
            stmt.close();
        }
    }

    public long getCount(ConnectionContext cc) throws NamingException, SQLException {
        StringBuffer sql = new StringBuffer("select count(*) from ");
        sql.append(cc.getDatabasePolicy().resolveTableName(this));
        PreparedStatement stmt = cc.getConnection().prepareStatement(sql.toString());
        try {
            ResultSet rs = stmt.executeQuery();
            try {
                if (rs.next())
                    return rs.getLong(1);
                else
                    return 0;
            } finally {
                rs.close();
            }
        } finally {
            stmt.close();
        }
    }

    /* ------------------------------------------------------------------------------------------------------------- */

    public void registerForeignKeyDependency(ForeignKey fKey) {
        // if we are the "referenced" foreign key, then the source is a child of ours
        if (fKey.getType() == ForeignKey.FKEYTYPE_PARENT) {
            Table parentTable = fKey.getReferencedColumns().getFirst().getTable();
            parentTable.registerChildTable(this);
        }
    }

    public void removeForeignKeyDependency(ForeignKey fKey) {
        // if we are the "referenced" foreign key, then the source is a child of ours
        if (fKey.getType() == ForeignKey.FKEYTYPE_PARENT) {
            Table parentTable = fKey.getReferencedColumns().getFirst().getTable();
            parentTable.removeChildTable(this);
        }
    }

    /* ------------------------------------------------------------------------------------------------------------- */

    public Indexes getIndexes() {
        return indexes;
    }

    public void addIndex(Index index) {
        indexes.add(index);
    }

    public Index createIndex() {
        return new BasicIndex(this);
    }

    public Index createIndex(Column column) {
        if (column.getTable() != this)
            throw new RuntimeException("Unable to create index -- column '" + column.getQualifiedName()
                    + "' does not belong to table '" + getName() + "'. (" + this + " != " + column.getTable()
                    + ")");
        else {
            Index index = new BasicIndex(column);
            index.setUnique(column.isUnique());
            return index;
        }
    }

    /* ------------------------------------------------------------------------------------------------------------- */

    public QueryExecutionLog getDmlExecutionLog() {
        return execLog;
    }

    public void insert(ConnectionContext cc, Row row) throws SQLException {
        for (int i = 0; i < triggers.length; i++)
            triggers[i].beforeTableRowInsert(cc, row);

        try {
            cc.getDatabasePolicy().insertValues(cc,
                    DatabasePolicy.DMLFLAG_EXECUTE | DatabasePolicy.DMLFLAG_USE_BIND_PARAMS, row.getColumnValues(),
                    row);
        } catch (NamingException e) {
            log.error("Error while inserting row " + row, e);
            throw new SQLException(e.getMessage());
        }

        for (int i = 0; i < triggers.length; i++)
            triggers[i].afterTableRowInsert(cc, row);

    }

    public void update(ConnectionContext cc, Row row) throws SQLException {
        update(cc, row, primaryKeyWhereClauseExpr, row.getPrimaryKeyValues().getValuesForSqlBindParams());
    }

    public void update(ConnectionContext cc, Row row, String whereCond, Object[] whereCondBindParams)
            throws SQLException {
        for (int i = 0; i < triggers.length; i++)
            triggers[i].beforeTableRowUpdate(cc, row);

        try {
            cc.getDatabasePolicy().updateValues(cc,
                    DatabasePolicy.DMLFLAG_EXECUTE | DatabasePolicy.DMLFLAG_USE_BIND_PARAMS, row.getColumnValues(),
                    row, whereCond, whereCondBindParams);
        } catch (NamingException e) {
            log.error("Error while updating row " + row + " whereCond " + whereCond, e);
            throw new SQLException(e.getMessage());
        }

        for (int i = 0; i < triggers.length; i++)
            triggers[i].afterTableRowUpdate(cc, row);
    }

    public void delete(ConnectionContext cc, Row row) throws SQLException {
        delete(cc, row, primaryKeyWhereClauseExpr, row.getPrimaryKeyValues().getValuesForSqlBindParams());
    }

    protected void deleteChildren(ConnectionContext cc, Row row, String whereCond, Object[] whereCondBindParams)
            throws NamingException, SQLException {
        Rows parentRows = getRowsByWhereCond(cc, whereCond, whereCondBindParams);
        for (int pr = 0; pr < parentRows.size(); pr++) {
            Row parentRow = parentRows.getRow(pr);

            Tables children = getChildTables();
            for (int ct = 0; ct < children.size(); ct++) {
                Table childTable = children.get(ct);

                Columns fKeyColumns = childTable.getForeignKeyColumns(ForeignKey.FKEYTYPE_PARENT);
                for (int fki = 0; fki < fKeyColumns.size(); fki++) {
                    ParentForeignKey fKey = (ParentForeignKey) fKeyColumns.get(fki).getForeignKey();
                    Rows childRows = fKey.getChildRowsByParentRow(cc, parentRow);

                    for (int cr = 0; cr < childRows.size(); cr++) {
                        Row childRow = childRows.getRow(cr);
                        childTable.delete(cc, childRow);
                    }
                }
            }
        }
    }

    public void delete(ConnectionContext cc, Row row, String whereCond, Object[] whereCondBindParams)
            throws SQLException {
        for (int i = 0; i < triggers.length; i++)
            triggers[i].beforeTableRowDelete(cc, row);

        final RowDeleteType rowDeleteType = getRowDeleteType();
        if (rowDeleteType.isCasadeChildren()) {
            try {
                deleteChildren(cc, row, whereCond, whereCondBindParams);
            } catch (NamingException e) {
                throw new SQLException(e.getMessage());
            }
        }

        try {
            cc.getDatabasePolicy().deleteValues(cc,
                    DatabasePolicy.DMLFLAG_EXECUTE | DatabasePolicy.DMLFLAG_USE_BIND_PARAMS, row.getColumnValues(),
                    row, whereCond, whereCondBindParams);
        } catch (NamingException e) {
            log.error("Error executing delete type '" + rowDeleteType + "' for row " + row + " whereCond "
                    + whereCond, e);
            throw new SQLException(e.getMessage());
        }

        for (int i = 0; i < triggers.length; i++)
            triggers[i].afterTableRowDelete(cc, row);
    }

    /**
     * Given a row full of data, either insert the record if no duplicates are found or update it if a duplicate
     * record is found.
     *
     * @param cc  The connection context
     * @param row The row with data to insert or update
     */
    public void insertOrUpdateIfDuplicateRowFound(ConnectionContext cc, Row row)
            throws NamingException, SQLException {
        final ColumnValues columnValues = row.getColumnValues();

        // if a record exists with the same primary key, do an immediate update and leave
        final Object[] valuesForSqlBindParams = row.getPrimaryKeyValues().getValuesForSqlBindParams();
        int nonNullsCount = 0;
        for (int i = 0; i < valuesForSqlBindParams.length; i++) {
            if (valuesForSqlBindParams[i] != null)
                nonNullsCount++;
        }
        if (nonNullsCount == valuesForSqlBindParams.length) {
            if (getAccessorByPrimaryKeyEquality().recordsExist(cc, valuesForSqlBindParams)) {
                update(cc, row);
                return;
            }
        }

        // now look to see if there are any rows with the same values as a unique index somewhere in the table; if so,
        // replace the existing row's data with this row using the primary key of the duplicate row that was found

        Indexes indexes = getIndexes();
        for (int j = 0; j < indexes.size(); j++) {
            Index index = indexes.get(j);
            if (!index.isUnique())
                continue;

            IndexColumns indexColumns = index.getColumns();
            Object[] params = new Object[indexColumns.size()];
            for (int k = 0; k < indexColumns.size(); k++) {
                //Iterate through all of the columns in the index
                Column indexColumn = indexColumns.get(k);
                ColumnValue columnValue = columnValues.getByColumnIndex(indexColumn.getIndexInRow());
                params[k] = columnValue.getValueForSqlBindParam();
            }

            QueryDefnSelect indexAccessor;
            indexAccessor = getAccessorByIndexEquality(index);
            QueryResultSet qrs = null;
            try {
                try {
                    qrs = indexAccessor.execute(cc, params, false);
                } catch (SQLException e) {
                    log.error("Error while checking for columns " + index.getColumns().getOnlyNames(",")
                            + " in unique index " + index.getName(), e);
                    continue;
                }
                ResultSet rs = qrs.getResultSet();
                if (rs.next()) {
                    Row duplicateRow = createRow();
                    duplicateRow.getColumnValues().populateValues(rs, 0);

                    // we want to use the same PK value as the duplicate row but we want to keep our own data
                    final PrimaryKeyColumnValues dupRowPKColVals = duplicateRow.getPrimaryKeyValues();
                    for (int k = 0; k < dupRowPKColVals.size(); k++)
                        columnValues.getByColumnIndex(k)
                                .setValue((ColumnValue) dupRowPKColVals.getByColumnIndex(k));

                    update(cc, row);
                    return;
                }
            } finally {
                if (qrs != null)
                    qrs.close(false);
            }
        }

        // no duplicate rows found, inserting a new recod instead
        insert(cc, row);
    }

    public void addTrigger(TableRowTrigger trigger) {
        TableRowTrigger[] result = new TableRowTrigger[triggers.length + 1];
        for (int i = 0; i < triggers.length; i++)
            result[i] = triggers[i];
        result[triggers.length] = trigger;
        triggers = result;
    }

    public TableRowTrigger createTrigger() {
        return null;
    }

    /* ------------------------------------------------------------------------------------------------------------- */

    public void initQueryDefinition(TableQueryDefinition tqd) {
        tqd.setName(getName());

        QueryDefnJoin join = tqd.createJoin();
        join.setName(getName());
        join.setTable(getName());
        if (isQuoteNameInSql())
            join.setFromExpr(getSqlName());
        join.setAutoInclude(true);
        tqd.addJoin(join);

        for (int i = 0; i < columns.size(); i++) {
            QueryDefnField field = columns.get(i).createQueryDefnField(tqd);
            try {
                if (field.getJoin() == null)
                    field.setJoin(join.getName());
            } catch (QueryDefinitionException e) {
                log.error("Error while initializing query definition.", e);
            }
            tqd.addField(field);
        }

        // automatically create and add the primary key accessor
        getAccessorByPrimaryKeyEquality();

        // automatically create and add the parent key accessor
        getAccessorByParentKeyEquality();

        // create all the accessors by single column equality
        for (int i = 0; i < columns.size(); i++) {
            Column column = columns.get(i);
            QueryDefnSelect selectByColumn = tqd.createSelect();
            selectByColumn.setName("by-" + column.getXmlNodeName() + "-equality");
            selectByColumn.setDistinct(false);
            tqd.addSelect(selectByColumn, new String[] { "by-" + column.getName() + "-equality" });

            try {
                populateTableColumnsAsDisplayFields(selectByColumn);
                populateColumnEqualityCondition(selectByColumn, column);
            } catch (QueryDefinitionException e) {
                log.error(e.getMessage(), e);
            }
        }

        // create all the accessors by index equality
        Indexes indexes = getIndexes();
        for (int i = 0; i < indexes.size(); i++) {
            Index index = indexes.get(i);

            QueryDefnSelect selectByIndex = tqd.createSelect();
            selectByIndex.setName("by-index-" + index.getName() + "-equality");
            selectByIndex.setDistinct(false);
            tqd.addSelect(selectByIndex);

            try {
                populateTableColumnsAsDisplayFields(selectByIndex);
                populateColumnEqualityConditions(selectByIndex, index.getColumns());
            } catch (QueryDefinitionException e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    protected void populateTableColumnsAsDisplayFields(QueryDefnSelect select) throws QueryDefinitionException {
        Columns columns = getColumns();
        for (int i = 0; i < columns.size(); i++) {
            QueryDefnFieldReference qdfr = select.createDisplay();
            qdfr.setField(columns.get(i).getName());
            select.addDisplay(qdfr);
        }
    }

    protected void populateColumnEqualityCondition(QueryDefnSelect qds, Column column)
            throws QueryDefnFieldNotFoundException, QueryDefnSqlComparisonNotFoundException {
        QueryDefnCondition cond = qds.createCondition();
        cond.setField(column.getName());
        cond.setAllowNull(true);

        SqlComparisonEnumeratedAttribute scea = new SqlComparisonEnumeratedAttribute();
        scea.setValue("equals");
        cond.setComparison(scea);

        qds.addCondition(cond);
    }

    protected void populateColumnEqualityConditions(QueryDefnSelect qds, Columns cols)
            throws QueryDefnFieldNotFoundException, QueryDefnSqlComparisonNotFoundException {
        int lastColumn = cols.size() - 1;
        for (int c = 0; c <= lastColumn; c++) {
            QueryDefnCondition cond = qds.createCondition();
            cond.setField(cols.get(c).getName());
            cond.setAllowNull(true);

            SqlComparisonEnumeratedAttribute scea = new SqlComparisonEnumeratedAttribute();
            scea.setValue("equals");
            cond.setComparison(scea);

            if (c < lastColumn) {
                QueryDefnConditionConnectorEnumeratedAttribute qdccea = new QueryDefnConditionConnectorEnumeratedAttribute();
                qdccea.setValue("and");
                cond.setConnector(qdccea);
            }

            qds.addCondition(cond);
        }
    }

    public QueryDefnSelect getAccessorByPrimaryKeyEquality() {
        if (selectByPrimaryKey == null) {
            TableQueryDefinition queryDefn = getQueryDefinition();
            selectByPrimaryKey = queryDefn.createSelect();
            selectByPrimaryKey.setName("by-primary-keys-equality");
            selectByPrimaryKey.setDistinct(false);
            queryDefn.addSelect(selectByPrimaryKey);

            try {
                populateTableColumnsAsDisplayFields(selectByPrimaryKey);
                populateColumnEqualityConditions(selectByPrimaryKey, getPrimaryKeyColumns());
            } catch (QueryDefinitionException e) {
                log.error(e.getMessage(), e);
            }

            PrimaryKeyColumns columns = getPrimaryKeyColumns();
            StringBuffer whereClausExprBuf = new StringBuffer();
            int lastColumn = columns.size() - 1;
            for (int c = 0; c <= lastColumn; c++) {
                if (c > 0)
                    whereClausExprBuf.append(" and ");
                whereClausExprBuf.append(columns.get(c).getName());
                whereClausExprBuf.append(" = ");
                whereClausExprBuf.append(" ? ");
            }
            primaryKeyWhereClauseExpr = whereClausExprBuf.toString();
        }

        return selectByPrimaryKey;
    }

    private QueryDefnSelect getAccessorByParentKeyEquality() {
        Columns parentRefCols = getParentRefColumns();
        if (selectByParentKey == null && parentRefCols.size() > 0) {
            TableQueryDefinition queryDefn = getQueryDefinition();
            selectByParentKey = queryDefn.createSelect();
            selectByParentKey.setName("by-parent-key-equality");
            selectByParentKey.setDistinct(false);
            queryDefn.addSelect(selectByParentKey);

            try {
                populateTableColumnsAsDisplayFields(selectByParentKey);
                populateColumnEqualityConditions(selectByParentKey, parentRefCols);
            } catch (QueryDefinitionException e) {
                log.error(e.getMessage(), e);
            }
        }

        return selectByParentKey;
    }

    public TableQueryDefinition getQueryDefinition() {
        if (queryDefn == null) {
            queryDefn = new TableQueryDefinition(this);
            initQueryDefinition(queryDefn);
        }

        return queryDefn;
    }

    public QueryDefnSelect createAccessor() throws QueryDefinitionException {
        return getQueryDefinition().createSelect();
    }

    public void addAccessor(QueryDefnSelect accessor) throws QueryDefinitionException {
        if (accessor.getDisplayFields().size() == 0)
            populateTableColumnsAsDisplayFields(accessor);

        getQueryDefinition().addSelect(accessor);
    }

    public QueryDefnSelect getAccessorByColumnEquality(Column column) {
        TableQueryDefinition tqd = getQueryDefinition();
        return tqd.getSelects().get("by-" + column.getXmlNodeName() + "-equality");
    }

    public QueryDefnSelect getAccessorByIndexEquality(Index index) {
        TableQueryDefinition tqd = getQueryDefinition();

        String accessorName = "by-index-" + index.getName() + "-equality";

        if (index.getColumns().size() == 1)
            accessorName = "by-" + index.getColumns().get(0).getName() + "-equality";

        return tqd.getSelects().get(accessorName);
    }

    public QueryDefnSelect getAccessorByColumnsEquality(Columns columns) {
        TableQueryDefinition tqd = getQueryDefinition();
        String accessorName = "by-" + columns.getOnlyXmlNodeNames("-") + "-equality";
        QueryDefnSelect qds = tqd.getSelects().get("by-" + columns.getOnlyXmlNodeNames("-") + "-equality");
        if (qds == null) {
            qds = queryDefn.createSelect();
            qds.setName(accessorName);
            qds.setDistinct(false);
            tqd.addSelect(qds);

            try {
                populateTableColumnsAsDisplayFields(qds);
                populateColumnEqualityConditions(qds, columns);
            } catch (QueryDefinitionException e) {
                log.error(e.getMessage(), e);
            }
        }
        return qds;
    }

    public String getLogicalDeleteUpdateSqlSetClauseFormat() {
        return logicalDeleteUpdateSqlSetClauseFormat;
    }

    public void setLogicalDeleteUpdateSqlSetClauseFormat(String logicalDeleteUpdateSqlSetClauseFormat) {
        this.logicalDeleteUpdateSqlSetClauseFormat = logicalDeleteUpdateSqlSetClauseFormat;
    }

    public RowDeleteType getRowDeleteType() {
        return rowDeleteType;
    }

    public void setRowDeleteType(RowDeleteType rowDeleteType) {
        this.rowDeleteType = rowDeleteType;
    }

    /* ------------------------------------------------------------------------------------------------------------- */

    public Query createQuery() {
        Query result = getSchema().getSqlManager().constructQuery();
        result.setNameSpace(this);
        return result;
    }

    public void addQuery(Query query) {
        getSchema().getSqlManager().appendQuery(query);
    }

    /* ------------------------------------------------------------------------------------------------------------- */

    protected void copySchemaRecordEditorDialogTemplate(Template dialogsPackageTemplate, TemplateElement elem,
            Map jexlVars) {
        TemplateElement dialogTemplate = dialogsPackageTemplate.addCopyOfChildAndReplaceExpressions(elem, jexlVars,
                false);

        boolean foundAnyDataElement = false;

        List copyChildren = elem.getChildren();
        for (int i = 0; i < copyChildren.size(); i++) {
            TemplateNode dialogChildNode = (TemplateNode) copyChildren.get(i);
            if (dialogChildNode instanceof TemplateElement) {
                TemplateElement dialogChildElem = (TemplateElement) dialogChildNode;
                final String elementName = dialogChildElem.getElementName();
                if (elementName.equals("data-type-presentation")) {
                    String colsFilterRegEx = dialogChildElem.getAttributes().getValue("columns");
                    if (colsFilterRegEx == null || colsFilterRegEx.length() == 0 || colsFilterRegEx.equals("*")) {
                        Columns columns = getColumns();
                        for (int c = 0; c < columns.size(); c++) {
                            Column column = columns.get(c);
                            column.addSchemaRecordEditorDialogTemplates(dialogTemplate, jexlVars);
                        }
                    } else {
                        Columns columns = getColumns();
                        for (int c = 0; c < columns.size(); c++) {
                            Column column = columns.get(c);
                            ValidationUtils.matchRegexp(column.getName(), colsFilterRegEx);
                            column.addSchemaRecordEditorDialogTemplates(dialogTemplate, jexlVars);
                        }
                    }
                } else {
                    if (elementName.equals("on-add-data") || elementName.equals("on-edit-data")
                            || elementName.equals("on-delete-data"))
                        foundAnyDataElement = true;
                    dialogTemplate.addCopyOfChildAndReplaceExpressions((TemplateElement) dialogChildNode, jexlVars,
                            true);
                }
            } else if (dialogChildNode instanceof TemplateText)
                dialogTemplate
                        .addChild(new TemplateText(dialogTemplate, ((TemplateText) dialogChildNode).getText()));
            else
                throw new RuntimeException("This should never happen.");
        }

        // if no meta data editor is provided, add defaults -- if any of the on-add/edit/delete-data templates are found
        // it means the user wants full control so we will leave it to them to implement
        if (!foundAnyDataElement) {
            TemplateElement addDataTemplateElement = dialogTemplate.addChild("on-add-data", null);
            addDataTemplateElement.addChild(getSchema().getName() + "." + getXmlNodeName(),
                    new String[][] { { "_auto-map", "*" } });

            TemplateElement editDataTemplateElement = dialogTemplate.addChild("on-edit-data", null);
            editDataTemplateElement.addChild(getSchema().getName() + "." + getXmlNodeName(),
                    new String[][] { { "_pk-value", "request:" + getPrimaryKeyColumns().getSole().getName() },
                            { "_auto-map", "*" } });

            TemplateElement deleteDataTemplateElement = dialogTemplate.addChild("on-delete-data", null);
            deleteDataTemplateElement.addChild(getSchema().getName() + "." + getXmlNodeName(),
                    new String[][] { { "_pk-value", "request:" + getPrimaryKeyColumns().getSole().getName() },
                            { "_auto-map", "*" } });
        }
    }

    public void addSchemaRecordEditorDialogTemplates(Template dialogsPackageTemplate) {
        TemplateProducer tablePresentation = getPresentation();
        List instances = tablePresentation.getInstances();

        if (instances.size() < 1)
            return;

        Map jexlVars = new HashMap();
        jexlVars.put("table", this);

        // only get the last instance since the final one is the one we're going to use (it will override earlier templates)
        Template tmpl = (Template) instances.get(instances.size() - 1);
        List presentationTmplChildren = tmpl.getChildren();
        for (int j = 0; j < presentationTmplChildren.size(); j++) {
            TemplateNode presentationTmplChildNode = (TemplateNode) presentationTmplChildren.get(j);
            if (presentationTmplChildNode instanceof TemplateElement) {
                TemplateElement elem = (TemplateElement) presentationTmplChildNode;
                if (elem.getElementName().equals("dialog"))
                    copySchemaRecordEditorDialogTemplate(dialogsPackageTemplate, elem, jexlVars);
                else
                    dialogsPackageTemplate.addCopyOfChildAndReplaceExpressions(elem, jexlVars, true);
            }
        }
    }

    /* ------------------------------------------------------------------------------------------------------------- */

    public Schema.TableTreeNode createTreeNode(Schema.TableTree owner, Schema.TableTreeNode parent, int level) {
        return new BasicTableTreeNode(owner, parent, level);
    }

    public class BasicTableTreeNode implements Schema.TableTreeNode {
        private Schema.TableTree owner;
        private Schema.TableTreeNode parent;
        private ParentForeignKey parentForeignKey;
        private int level;
        private List children = new ArrayList();
        private List ancestors;

        public BasicTableTreeNode(Schema.TableTree owner, Schema.TableTreeNode parent, int level) {
            this.owner = owner;
            this.parent = parent;
            this.level = level;

            Columns parentRefColumns = getParentRefColumns();
            for (int i = 0; i < parentRefColumns.size(); i++) {
                Column parentRefColumn = parentRefColumns.get(i);
                ParentForeignKey fKey = (ParentForeignKey) parentRefColumn.getForeignKey();
                if (fKey.getReferencedColumns().getFirst().getTable() == parent.getTable())
                    parentForeignKey = fKey;
            }

            Set sortedChildren = new TreeSet(BasicSchema.TABLE_TREE_NODE_COMPARATOR);
            Tables childTables = getChildTables();
            for (int i = 0; i < childTables.size(); i++) {
                Table childTable = childTables.get(i);
                sortedChildren.add(childTable.createTreeNode(owner, this, level + 1));
            }
            children.addAll(sortedChildren);
        }

        public Schema.TableTree getOwner() {
            return owner;
        }

        public Table getTable() {
            return BasicTable.this;
        }

        public Schema.TableTreeNode getParentNode() {
            return parent;
        }

        public ParentForeignKey getParentForeignKey() {
            return parentForeignKey;
        }

        public List getChildren() {
            return children;
        }

        public String getAncestorTableNames(String delimiter) {
            List ancestors = getAncestors();
            if (ancestors.size() == 0)
                return null;

            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < ancestors.size(); i++) {
                Schema.TableTreeNode node = (Schema.TableTreeNode) ancestors.get(i);
                if (i > 0)
                    sb.append(delimiter);
                sb.append(node.getTable().getName());
            }
            return sb.toString();
        }

        public List getAncestors() {
            if (ancestors == null) {
                ancestors = new ArrayList();
                Schema.TableTreeNode activeParentNode = getParentNode();
                while (activeParentNode != null) {
                    if (ancestors.size() == 0)
                        ancestors.add(activeParentNode);
                    else
                        ancestors.add(0, activeParentNode);

                    activeParentNode = activeParentNode.getParentNode();
                }
            }

            return ancestors;
        }

        public boolean hasChildren() {
            return children.size() > 0;
        }

        public boolean hasGrandchildren() {
            if (!hasChildren())
                return false;

            for (int i = 0; i < children.size(); i++) {
                // if any of our children have children then we have grandchildren
                if (((Schema.TableTreeNode) children.get(i)).hasChildren())
                    return true;
            }

            return false;
        }

        public String toString() {
            StringBuffer sb = new StringBuffer();

            for (int i = 0; i < level; i++)
                sb.append("  ");
            sb.append(getTable().getName());
            sb.append(" [");
            sb.append(parentForeignKey);
            sb.append("] (");
            sb.append(getAncestorTableNames("."));
            sb.append(")");
            sb.append("\n");

            for (int c = 0; c < children.size(); c++)
                sb.append(children.get(c));

            return sb.toString();
        }
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("Table " + getName() + "\n");
        sb.append(columns.toString());
        return sb.toString();
    }
}