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