    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/io/datastore/sql/transaction/delete/DeleteHandler.java $
    002    /*----------------------------------------------------------------------------
    003     This file is part of deegree, http://deegree.org/
    004     Copyright (C) 2001-2009 by:
    005       Department of Geography, University of Bonn
    006     and
    007       lat/lon GmbH
    008
    009     This library is free software; you can redistribute it and/or modify it under
    010     the terms of the GNU Lesser General Public License as published by the Free
    011     Software Foundation; either version 2.1 of the License, or (at your option)
    012     any later version.
    013     This library is distributed in the hope that it will be useful, but WITHOUT
    014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
    016     details.
    017     You should have received a copy of the GNU Lesser General Public License
    018     along with this library; if not, write to the Free Software Foundation, Inc.,
    019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    020
    021     Contact information:
    022
    023     lat/lon GmbH
    024     Aennchenstr. 19, 53177 Bonn
    025     Germany
    026     http://lat-lon.de/
    027
    028     Department of Geography, University of Bonn
    029     Prof. Dr. Klaus Greve
    030     Postfach 1147, 53001 Bonn
    031     Germany
    032     http://www.geographie.uni-bonn.de/deegree/
    033
    034     e-mail: info@deegree.org
    035    ----------------------------------------------------------------------------*/
    036    package org.deegree.io.datastore.sql.transaction.delete;
    037
    038    import java.sql.Connection;
    039    import java.sql.PreparedStatement;
    040    import java.sql.ResultSet;
    041    import java.sql.SQLException;
    042    import java.util.ArrayList;
    043    import java.util.Collection;
    044    import java.util.List;
    045
    046    import org.deegree.datatypes.Types;
    047    import org.deegree.framework.log.ILogger;
    048    import org.deegree.framework.log.LoggerFactory;
    049    import org.deegree.i18n.Messages;
    050    import org.deegree.io.datastore.Datastore;
    051    import org.deegree.io.datastore.DatastoreException;
    052    import org.deegree.io.datastore.FeatureId;
    053    import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
    054    import org.deegree.io.datastore.schema.MappedFeatureType;
    055    import org.deegree.io.datastore.schema.MappedGMLSchema;
    056    import org.deegree.io.datastore.schema.MappedPropertyType;
    057    import org.deegree.io.datastore.schema.TableRelation;
    058    import org.deegree.io.datastore.schema.content.MappingField;
    059    import org.deegree.io.datastore.sql.AbstractRequestHandler;
    060    import org.deegree.io.datastore.sql.StatementBuffer;
    061    import org.deegree.io.datastore.sql.TableAliasGenerator;
    062    import org.deegree.io.datastore.sql.transaction.SQLTransaction;
    063    import org.deegree.io.datastore.sql.transaction.UpdateHandler;
    064    import org.deegree.model.feature.schema.FeatureType;
    065    import org.deegree.model.feature.schema.PropertyType;
    066    import org.deegree.model.filterencoding.Filter;
    067    import org.deegree.ogcwebservices.wfs.operation.transaction.Delete;
    068    import org.deegree.ogcwebservices.wfs.operation.transaction.Transaction;
    069
    070    /**
    071     * Handler for {@link Delete} operations (which usually occur as parts of {@link Transaction} requests).
    072     * <p>
    073     * When a {@link Delete} operation is performed, the following actions are taken:
    074     * <ul>
    075     * <li>the {@link FeatureId}s of all (root) feature instances that match the associated {@link Filter} are determined</li>
    076     * <li>the {@link FeatureGraph} is built in order to determine which features may be deleted without removing
    077     * subfeatures of independent features</li>
    078     * <li>the {@link TableGraph} is built that contains explicit information on all table rows that have to be deleted (and
    079     * their dependencies)</li>
    080     * <li>the {@link TableNode}s of the {@link TableGraph} are sorted in topological order, i.e. they may be deleted in
    081     * that order without violating any foreign key constraints</li>
    082     * </ul>
    083     *
    084     * @see FeatureGraph
    085     * @see TableGraph
    086     *
    087     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
    088     * @author last edited by: $Author: mschneider $
    089     *
    090     * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
    091     */
    092    public class DeleteHandler extends AbstractRequestHandler {
    093
    094        private static final ILogger LOG = LoggerFactory.getLogger( DeleteHandler.class );
    095
    096        private String lockId;
    097
    098        /**
    099         * Creates a new <code>DeleteHandler</code> from the given parameters.
    100         *
    101         * @param dsTa
    102         * @param aliasGenerator
    103         * @param conn
    104         * @param lockId
    105         *            optional id of associated lock (may be null)
    106         */
    107        public DeleteHandler( SQLTransaction dsTa, TableAliasGenerator aliasGenerator, Connection conn, String lockId ) {
    108            super( dsTa.getDatastore(), aliasGenerator, conn );
    109            this.lockId = lockId;
    110        }
    111
    112        /**
    113         * Deletes the features from the {@link Datastore} that have a certain type and are matched by the given filter.
    114         *
    115         * @param ft
    116         *            non-abstract feature type of the features to be deleted
    117         * @param filter
    118         *            constraints the feature instances to be deleted
    119         * @return number of deleted feature instances
    120         * @throws DatastoreException
    121         */
    122        public int performDelete( MappedFeatureType ft, Filter filter )
    123                                throws DatastoreException {
    124
    125            assert !ft.isAbstract();
    126
    127            if ( !ft.isDeletable() ) {
    128                String msg = Messages.getMessage( "DATASTORE_FT_NOT_DELETABLE", ft.getName() );
    129                throw new DatastoreException( msg );
    130            }
    131
    132            List<FeatureId> fids = determineAffectedAndModifiableFIDs( ft, filter, this.lockId );
    133
    134            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
    135                LOG.logDebug( "Affected fids:" );
    136                for ( FeatureId fid : fids ) {
    137                    LOG.logDebug( "" + fid );
    138                }
    139            }
    140
    141            FeatureGraph featureGraph = new FeatureGraph( fids, this );
    142            TableGraph tableGraph = new TableGraph( featureGraph, this );
    143
    144            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
    145                LOG.logDebug( "FeatureGraph: " + featureGraph );
    146                LOG.logDebug( "TableGraph: " + tableGraph );
    147            }
    148
    149            List<TableNode> sortedNodes = tableGraph.getDeletionOrder();
    150            for ( TableNode node : sortedNodes ) {
    151                boolean delete = true;
    152                if ( node.isDeleteVetoPossible() ) {
    153                    List<TableNode> referencingRows = getReferencingRows( node );
    154                    if ( referencingRows.size() > 0 ) {
    155                        delete = false;
    156                        LOG.logDebug( "Skipping delete of " + node + ": " + referencingRows.size() + " reference(s) exist." );
    157                        for ( TableNode referencingNode : referencingRows ) {
    158                            LOG.logDebug( "Referenced by: " + referencingNode );
    159                        }
    160                    }
    161                }
    162                if ( delete ) {
    163                    performDelete( node );
    164                }
    165            }
    166
    167            int deletedFeatures = tableGraph.getDeletableRootFeatureCount();
    168
    169            if ( deletedFeatures != fids.size() ) {
    170                String msg = Messages.getMessage( "DATASTORE_COULD_NOT_DELETE_ALL" );
    171                LOG.logInfo( msg );
    172            }
    173
    174            // return count of actually deleted (root) features
    175            return deletedFeatures;
    176        }
    177
    178        /**
    179         * Deletes the table entry from the SQL database that is represented by the given {@link TableNode}.
    180         *
    181         * @param node
    182         * @throws DatastoreException
    183         */
    184        private void performDelete( TableNode node )
    185                                throws DatastoreException {
    186
    187            StatementBuffer query = new StatementBuffer();
    188            query.append( "DELETE FROM " );
    189            query.append( node.getTable() );
    190            query.append( " WHERE " );
    191            boolean first = true;
    192            for ( KeyColumn column : node.getKeyColumns() ) {
    193                if ( first ) {
    194                    first = false;
    195                } else {
    196                    query.append( " AND " );
    197                }
    198                query.append( column.getName() );
    199                query.append( "=?" );
    200                query.addArgument( column.getValue(), column.getTypeCode() );
    201            }
    202
    203            PreparedStatement stmt = null;
    204            try {
    205                stmt = this.datastore.prepareStatement( conn, query );
    206                LOG.logDebug( "Deleting row: " + query );
    207                stmt.execute();
    208            } catch ( SQLException e ) {
    209                String msg = "Error performing delete '" + query + "': " + e.getMessage();
    210                LOG.logInfo( msg, e );
    211                throw new DatastoreException( msg );
    212            } finally {
    213                if ( stmt != null ) {
    214                    try {
    215                        stmt.close();
    216                    } catch ( SQLException e ) {
    217                        String msg = "Error closing statement: " + e.getMessage();
    218                        LOG.logError( msg, e );
    219                    }
    220                }
    221            }
    222        }
    223
    224        /**
    225         * Determines the {@link TableNode} that represent the simple/geometry properties in the property table attached by
    226         * the given {@link TableRelation}.
    227         *
    228         * @param fid
    229         *            id of the feature that owns the properties
    230         * @param relation
    231         *            describes how the property table is joined to the feature table
    232         * @return the simple/geometry properties in the the related property table
    233         * @throws DatastoreException
    234         */
    235        List<TableNode> determinePropNodes( FeatureId fid, TableRelation relation )
    236                                throws DatastoreException {
    237
    238            List<TableNode> propEntries = new ArrayList<TableNode>();
    239
    240            this.aliasGenerator.reset();
    241            String fromAlias = this.aliasGenerator.generateUniqueAlias();
    242            String toAlias = this.aliasGenerator.generateUniqueAlias();
    243            MappingField[] fromFields = relation.getFromFields();
    244            MappingField[] toFields = relation.getToFields();
    245
    246            StatementBuffer query = new StatementBuffer();
    247            query.append( "SELECT DISTINCT " );
    248            for ( int i = 0; i < toFields.length; i++ ) {
    249                query.append( toAlias );
    250                query.append( "." );
    251                query.append( toFields[i].getField() );
    252                if ( i != toFields.length - 1 ) {
    253                    query.append( ',' );
    254                }
    255            }
    256            query.append( " FROM " );
    257            query.append( fid.getFeatureType().getTable() );
    258            query.append( " " );
    259            query.append( fromAlias );
    260            query.append( " INNER JOIN " );
    261            query.append( relation.getToTable() );
    262            query.append( " " );
    263            query.append( toAlias );
    264            query.append( " ON " );
    265            for ( int j = 0; j < fromFields.length; j++ ) {
    266                query.append( fromAlias );
    267                query.append( '.' );
    268                query.append( fromFields[j].getField() );
    269                query.append( '=' );
    270                query.append( toAlias );
    271                query.append( '.' );
    272                query.append( toFields[j].getField() );
    273            }
    274            query.append( " WHERE " );
    275            appendFeatureIdConstraint( query, fid, fromAlias );
    276
    277            PreparedStatement stmt = null;
    278            ResultSet rs = null;
    279            try {
    280                stmt = this.datastore.prepareStatement( conn, query );
    281                LOG.logDebug( "Performing: " + query );
    282                rs = stmt.executeQuery();
    283                while ( rs.next() ) {
    284                    Collection<KeyColumn> keyColumns = new ArrayList<KeyColumn>();
    285                    for ( int i = 0; i < toFields.length; i++ ) {
    286                        KeyColumn column = new KeyColumn( toFields[i].getField(), toFields[i].getType(),
    287                                                          rs.getObject( i + 1 ) );
    288                        keyColumns.add( column );
    289                    }
    290                    TableNode propEntry = new TableNode( relation.getToTable(), keyColumns );
    291                    propEntries.add( propEntry );
    292                }
    293            } catch ( SQLException e ) {
    294                LOG.logInfo( e.getMessage(), e );
    295                throw new DatastoreException( "Error in addPropertyNodes(): " + e.getMessage() );
    296            } finally {
    297                try {
    298                    if ( rs != null ) {
    299                        try {
    300                            rs.close();
    301                        } catch ( SQLException e ) {
    302                            LOG.logError( "Error closing result set: '" + e.getMessage() + "'.", e );
    303                        }
    304                    }
    305                } finally {
    306                    if ( stmt != null ) {
    307                        try {
    308                            stmt.close();
    309                        } catch ( SQLException e ) {
    310                            LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
    311                        }
    312                    }
    313                }
    314            }
    315            return propEntries;
    316        }
    317
    318        /**
    319         * Determines the row in the join table that connects a certain feature with a subfeature.
    320         *
    321         * @param fid
    322         *            id of the (super-) feature
    323         * @param subFid
    324         *            id of the subfeature
    325         * @param relation1
    326         *            describes how the join table is attached
    327         * @param relation2
    328         *            describes how the subfeature table is joined
    329         * @return join table row (as a {@link TableNode})
    330         * @throws DatastoreException
    331         */
    332        TableNode determineJTNode( FeatureId fid, FeatureId subFid, TableRelation relation1, TableRelation relation2 )
    333                                throws DatastoreException {
    334
    335            LOG.logDebug( "Determining join table entry for feature " + fid + " and subfeature " + subFid );
    336            TableNode jtEntry = null;
    337
    338            this.aliasGenerator.reset();
    339
    340            String featureTableAlias = this.aliasGenerator.generateUniqueAlias();
    341            String joinTableAlias = this.aliasGenerator.generateUniqueAlias();
    342            String subFeatureTableAlias = this.aliasGenerator.generateUniqueAlias();
    343
    344            MappingField[] fromFields = relation1.getFromFields();
    345            MappingField[] fromFields2 = relation2.getFromFields();
    346            MappingField[] toFields = relation1.getToFields();
    347            MappingField[] toFields2 = relation2.getToFields();
    348
    349            // need to select 'from' fields of second relation element as well
    350            MappingField[] selectFields = new MappingField[toFields.length + fromFields2.length];
    351            for ( int i = 0; i < toFields.length; i++ ) {
    352                selectFields[i] = toFields[i];
    353            }
    354            for ( int i = 0; i < fromFields2.length; i++ ) {
    355                selectFields[i + toFields.length] = fromFields2[i];
    356            }
    357
    358            StatementBuffer query = new StatementBuffer();
    359            query.append( "SELECT DISTINCT " );
    360            for ( int i = 0; i < selectFields.length; i++ ) {
    361                query.append( joinTableAlias );
    362                query.append( "." );
    363                query.append( selectFields[i].getField() );
    364                if ( i != selectFields.length - 1 ) {
    365                    query.append( ',' );
    366                }
    367            }
    368            query.append( " FROM " );
    369            query.append( fid.getFeatureType().getTable() );
    370            query.append( " " );
    371            query.append( featureTableAlias );
    372            query.append( " INNER JOIN " );
    373            query.append( relation1.getToTable() );
    374            query.append( " " );
    375            query.append( joinTableAlias );
    376            query.append( " ON " );
    377            for ( int j = 0; j < fromFields.length; j++ ) {
    378                query.append( featureTableAlias );
    379                query.append( '.' );
    380                query.append( fromFields[j].getField() );
    381                query.append( '=' );
    382                query.append( joinTableAlias );
    383                query.append( '.' );
    384                query.append( toFields[j].getField() );
    385            }
    386            query.append( " INNER JOIN " );
    387            query.append( subFid.getFeatureType().getTable() );
    388            query.append( " " );
    389            query.append( subFeatureTableAlias );
    390            query.append( " ON " );
    391            for ( int j = 0; j < fromFields2.length; j++ ) {
    392                query.append( joinTableAlias );
    393                query.append( '.' );
    394                query.append( fromFields2[j].getField() );
    395                query.append( '=' );
    396                query.append( subFeatureTableAlias );
    397                query.append( '.' );
    398                query.append( toFields2[j].getField() );
    399            }
    400
    401            query.append( " WHERE " );
    402            appendFeatureIdConstraint( query, fid, featureTableAlias );
    403            query.append( " AND " );
    404            appendFeatureIdConstraint( query, subFid, subFeatureTableAlias );
    405
    406            PreparedStatement stmt = null;
    407            ResultSet rs = null;
    408            try {
    409                stmt = this.datastore.prepareStatement( conn, query );
    410                LOG.logDebug( "Determining join table row: " + query );
    411                rs = stmt.executeQuery();
    412                if ( rs.next() ) {
    413                    Collection<KeyColumn> keyColumns = new ArrayList<KeyColumn>( selectFields.length );
    414                    for ( int i = 0; i < selectFields.length; i++ ) {
    415                        KeyColumn column = new KeyColumn( selectFields[i].getField(), selectFields[i].getType(),
    416                                                          rs.getObject( i + 1 ) );
    417                        keyColumns.add( column );
    418                    }
    419
    420                    if ( subFid.getFeatureType().hasSeveralImplementations() ) {
    421                        String localSubFtName = subFid.getFeatureType().getName().getLocalName();
    422                        KeyColumn column = new KeyColumn( FT_COLUMN, Types.VARCHAR, localSubFtName );
    423                        keyColumns.add( column );
    424                    }
    425                    jtEntry = new TableNode( relation1.getToTable(), keyColumns );
    426                } else {
    427                    String msg = "This is impossible: No join table row between feature and subfeature!?";
    428                    throw new DatastoreException( msg );
    429                }
    430            } catch ( SQLException e ) {
    431                LOG.logInfo( e.getMessage(), e );
    432                throw new DatastoreException( "Error in determineJTNode(): " + e.getMessage() );
    433            } finally {
    434                try {
    435                    if ( rs != null ) {
    436                        try {
    437                            rs.close();
    438                        } catch ( SQLException e ) {
    439                            LOG.logError( "Error closing result set: '" + e.getMessage() + "'.", e );
    440                        }
    441                    }
    442                } finally {
    443                    if ( stmt != null ) {
    444                        try {
    445                            stmt.close();
    446                        } catch ( SQLException e ) {
    447                            LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
    448                        }
    449                    }
    450                }
    451            }
    452            return jtEntry;
    453        }
    454
    455        /**
    456         * Delete orphaned rows in the specified property table (target table of the given {@link TableRelation}).
    457         * <p>
    458         * Only used by the {@link UpdateHandler}.
    459         *
    460         * @param relation
    461         * @param keyValues
    462         * @throws DatastoreException
    463         */
    464        public void deleteOrphanedPropertyRows( TableRelation relation, Object[] keyValues )
    465                                throws DatastoreException {
    466            Collection<KeyColumn> keyColumns = new ArrayList<KeyColumn>( keyValues.length );
    467            for ( int i = 0; i < keyValues.length; i++ ) {
    468                KeyColumn keyColumn = new KeyColumn( relation.getToFields()[i].getField(),
    469                                                     relation.getToFields()[i].getType(), keyValues[i] );
    470                keyColumns.add( keyColumn );
    471            }
    472            TableNode node = new TableNode( relation.getToTable(), keyColumns );
    473            if ( getReferencingRows( node ).size() == 0 ) {
    474                performDelete( node );
    475            }
    476        }
    477
    478        /**
    479         * Returns all table rows that reference the given table row ({@link TableNode}).
    480         *
    481         * @param node
    482         * @return all table rows that reference the given table row
    483         * @throws DatastoreException
    484         */
    485        private List<TableNode> getReferencingRows( TableNode node )
    486                                throws DatastoreException {
    487
    488            List<TableNode> rows = new ArrayList<TableNode>();
    489            for ( TableReference tableReference : getReferencingTables( node.getTable() ) ) {
    490                rows.addAll( getReferencingRows( node, tableReference ) );
    491            }
    492            return rows;
    493        }
    494
    495        /**
    496         * Returns all stored rows (as {@link TableNode}s) that reference the given row ({@link TableNode}) via the also
    497         * given reference relation.
    498         *
    499         * @param node
    500         * @param ref
    501         * @return all stored rows that reference the given row
    502         * @throws DatastoreException
    503         */
    504        private List<TableNode> getReferencingRows( TableNode node, TableReference ref )
    505                                throws DatastoreException {
    506
    507            List<TableNode> referencingRows = new ArrayList<TableNode>();
    508            this.aliasGenerator.reset();
    509            String fromAlias = this.aliasGenerator.generateUniqueAlias();
    510            String toAlias = this.aliasGenerator.generateUniqueAlias();
    511            MappingField[] fromFields = ref.getFkColumns();
    512            MappingField[] toFields = ref.getKeyColumns();
    513
    514            StatementBuffer query = new StatementBuffer();
    515            query.append( "SELECT DISTINCT " );
    516            for ( int i = 0; i < fromFields.length; i++ ) {
    517                query.append( fromAlias );
    518                query.append( "." );
    519                query.append( fromFields[i].getField() );
    520                if ( i != fromFields.length - 1 ) {
    521                    query.append( ',' );
    522                }
    523            }
    524            query.append( " FROM " );
    525            query.append( ref.getFromTable() );
    526            query.append( " " );
    527            query.append( fromAlias );
    528            query.append( " INNER JOIN " );
    529            query.append( ref.getToTable() );
    530            query.append( " " );
    531            query.append( toAlias );
    532            query.append( " ON " );
    533            for ( int j = 0; j < fromFields.length; j++ ) {
    534                query.append( fromAlias );
    535                query.append( '.' );
    536                query.append( fromFields[j].getField() );
    537                query.append( '=' );
    538                query.append( toAlias );
    539                query.append( '.' );
    540                query.append( toFields[j].getField() );
    541            }
    542            query.append( " WHERE " );
    543            int i = node.getKeyColumns().size();
    544            for ( KeyColumn column : node.getKeyColumns() ) {
    545                query.append( toAlias );
    546                query.append( '.' );
    547                query.append( column.getName() );
    548                query.append( "=?" );
    549                query.addArgument( column.getValue(), column.getTypeCode() );
    550                if ( --i != 0 ) {
    551                    query.append( " AND " );
    552                }
    553            }
    554
    555            PreparedStatement stmt = null;
    556            ResultSet rs = null;
    557            try {
    558                stmt = this.datastore.prepareStatement( conn, query );
    559                LOG.logDebug( "Performing: " + query );
    560                rs = stmt.executeQuery();
    561                while ( rs.next() ) {
    562                    Collection<KeyColumn> keyColumns = new ArrayList<KeyColumn>( fromFields.length );
    563                    for ( i = 0; i < fromFields.length; i++ ) {
    564                        KeyColumn column = new KeyColumn( fromFields[i].getField(), fromFields[i].getType(),
    565                                                          rs.getObject( i + 1 ) );
    566                        keyColumns.add( column );
    567                    }
    568                    TableNode referencingRow = new TableNode( ref.getFromTable(), keyColumns );
    569                    referencingRows.add( referencingRow );
    570                }
    571            } catch ( SQLException e ) {
    572                throw new DatastoreException( "Error in getReferencingRows(): " + e.getMessage() );
    573            } finally {
    574                try {
    575                    if ( rs != null ) {
    576                        try {
    577                            rs.close();
    578                        } catch ( SQLException e ) {
    579                            LOG.logError( "Error closing result set: '" + e.getMessage() + "'.", e );
    580                        }
    581                    }
    582                } finally {
    583                    if ( stmt != null ) {
    584                        try {
    585                            stmt.close();
    586                        } catch ( SQLException e ) {
    587                            LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
    588                        }
    589                    }
    590                }
    591            }
    592            return referencingRows;
    593        }
    594
    595        /**
    596         * Returns all tables that reference the given table.
    597         *
    598         * TODO cache search
    599         *
    600         * @param table
    601         * @return all tables that reference the given table
    602         */
    603        private List<TableReference> getReferencingTables( String table ) {
    604
    605            List<TableReference> tables = new ArrayList<TableReference>();
    606            MappedGMLSchema[] schemas = this.datastore.getSchemas();
    607            for ( int i = 0; i < schemas.length; i++ ) {
    608                MappedGMLSchema schema = schemas[i];
    609                FeatureType[] fts = schema.getFeatureTypes();
    610                for ( int j = 0; j < fts.length; j++ ) {
    611                    MappedFeatureType ft = (MappedFeatureType) fts[j];
    612                    if ( !ft.isAbstract() ) {
    613                        PropertyType[] props = ft.getProperties();
    614                        for ( int k = 0; k < props.length; k++ ) {
    615                            tables.addAll( getReferencingTables( (MappedPropertyType) props[k], table ) );
    616                        }
    617                    }
    618                }
    619            }
    620            return tables;
    621        }
    622
    623        /**
    624         * Returns all tables that reference the given table and that are defined in the mapping of the given property type.
    625         *
    626         * @param property
    627         * @param table
    628         * @return all tables that reference the given table
    629         */
    630        private List<TableReference> getReferencingTables( MappedPropertyType property, String table ) {
    631
    632            List<TableReference> tables = new ArrayList<TableReference>();
    633            if ( property instanceof MappedFeaturePropertyType
    634                 && ( (MappedFeaturePropertyType) property ).getFeatureTypeReference().getFeatureType().isAbstract() ) {
    635                TableRelation[] relations = property.getTableRelations();
    636                for ( int j = 0; j < relations.length - 1; j++ ) {
    637                    TableReference ref = new TableReference( relations[j] );
    638                    if ( ref.getToTable().equals( table ) ) {
    639                        tables.add( ref );
    640                    }
    641                }
    642                MappedFeaturePropertyType pt = (MappedFeaturePropertyType) property;
    643                MappedFeatureType abstractFt = pt.getFeatureTypeReference().getFeatureType();
    644                MappedFeatureType[] substitutions = abstractFt.getConcreteSubstitutions();
    645                for ( MappedFeatureType concreteType : substitutions ) {
    646                    TableRelation finalStep = relations[relations.length - 1];
    647                    TableReference ref = new TableReference( getTableRelation( finalStep, concreteType.getTable() ) );
    648                    if ( ref.getToTable().equals( table ) ) {
    649                        tables.add( ref );
    650                    }
    651                }
    652
    653            } else {
    654                TableRelation[] relations = property.getTableRelations();
    655                for ( int j = 0; j < relations.length; j++ ) {
    656                    TableReference ref = new TableReference( relations[j] );
    657                    if ( ref.getToTable().equals( table ) ) {
    658                        tables.add( ref );
    659                    }
    660                }
    661            }
    662            return tables;
    663        }
    664
    665        private TableRelation getTableRelation( TableRelation toAbstractSubFt, String table ) {
    666            MappingField[] toConcreteFields = new MappingField[toAbstractSubFt.getToFields().length];
    667            for ( int i = 0; i < toConcreteFields.length; i++ ) {
    668                MappingField toAbstractField = toAbstractSubFt.getToFields()[i];
    669                toConcreteFields[i] = new MappingField( table, toAbstractField.getField(), toAbstractField.getType() );
    670            }
    671            TableRelation toConcreteSubFt = new TableRelation( toAbstractSubFt.getFromFields(), toConcreteFields,
    672                                                               toAbstractSubFt.getFKInfo(),
    673                                                               toAbstractSubFt.getIdGenerator() );
    674            return toConcreteSubFt;
    675        }
    676    }