001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/io/datastore/sql/AbstractRequestHandler.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;
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.HashMap;
051    import java.util.HashSet;
052    import java.util.List;
053    import java.util.Map;
054    import java.util.Set;
055    
056    import org.deegree.framework.log.ILogger;
057    import org.deegree.framework.log.LoggerFactory;
058    import org.deegree.i18n.Messages;
059    import org.deegree.io.datastore.DatastoreException;
060    import org.deegree.io.datastore.FeatureId;
061    import org.deegree.io.datastore.LockManager;
062    import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
063    import org.deegree.io.datastore.schema.MappedFeatureType;
064    import org.deegree.io.datastore.schema.MappedGMLId;
065    import org.deegree.io.datastore.schema.MappedGMLSchema;
066    import org.deegree.io.datastore.schema.MappedPropertyType;
067    import org.deegree.io.datastore.schema.TableRelation;
068    import org.deegree.io.datastore.schema.content.MappingField;
069    import org.deegree.io.datastore.sql.wherebuilder.WhereBuilder;
070    import org.deegree.model.feature.schema.FeatureType;
071    import org.deegree.model.feature.schema.PropertyType;
072    import org.deegree.model.filterencoding.Filter;
073    
074    /**
075     * This abstract class implements some common SQL functionality needed by request handlers for SQL
076     * based datastores.
077     * 
078     * @see QueryHandler
079     * 
080     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
081     * @author last edited by: $Author: mschneider $
082     * 
083     * @version $Revision: 7737 $, $Date: 2007-07-09 14:09:59 +0200 (Mo, 09 Jul 2007) $
084     */
085    public class AbstractRequestHandler {
086    
087        private static final ILogger LOG = LoggerFactory.getLogger( AbstractRequestHandler.class );
088    
089        /**
090         * Column used for disambiguation of feature properties that contain features that have more
091         * than one concrete type.
092         */
093        protected static final String FT_COLUMN = "featuretype";
094    
095        /**
096         * Column prefix used for disambiguation of feature properties that contain features that have
097         * more than one concrete type.
098         */
099        protected static final String FT_PREFIX = "FT_";
100    
101        protected AbstractSQLDatastore datastore;
102    
103        protected TableAliasGenerator aliasGenerator;
104    
105        protected Connection conn;
106    
107        /**
108         * Creates a new instance of <code>AbstractRequestHandler</code> from the given parameters.
109         * 
110         * @param ds
111         * @param aliasGenerator
112         * @param conn
113         */
114        public AbstractRequestHandler( AbstractSQLDatastore ds, TableAliasGenerator aliasGenerator, Connection conn ) {
115            this.datastore = ds;
116            this.aliasGenerator = aliasGenerator;
117            this.conn = conn;
118        }
119    
120        /**
121         * Determines the feature ids that are matched by the given filter.
122         * 
123         * @param ft
124         *            non-abstract feature type
125         * @param filter
126         *            constraints the feature instances
127         * @return the feature ids that are matched by the given filter
128         * @throws DatastoreException
129         */
130        public List<FeatureId> determineAffectedFIDs( MappedFeatureType ft, Filter filter )
131                                throws DatastoreException {
132    
133            assert !ft.isAbstract();
134    
135            TableAliasGenerator aliasGenerator = new TableAliasGenerator();
136            VirtualContentProvider vcProvider = new VirtualContentProvider( filter, this.datastore, this.conn );
137            WhereBuilder whereBuilder = this.datastore.getWhereBuilder( new MappedFeatureType[] { ft }, null, filter, null,
138                                                                        aliasGenerator, vcProvider );
139    
140            // if no filter is given
141            StatementBuffer query = buildInitialFIDSelect( ft, whereBuilder );
142            LOG.logDebug( "Determine affected feature id query: '" + query + "'" );
143    
144            List<FeatureId> fids = null;
145            PreparedStatement stmt = null;
146            ResultSet rs = null;
147            try {
148                stmt = this.datastore.prepareStatement( conn, query );
149                rs = stmt.executeQuery();
150                fids = extractFeatureIds( rs, ft );
151            } catch ( SQLException e ) {
152                throw new DatastoreException( "Error while determining affected features of type: '" + ft.getName() + "': "
153                                              + e.getMessage() );
154            } finally {
155                try {
156                    if ( rs != null ) {
157                        try {
158                            rs.close();
159                        } catch ( SQLException e ) {
160                            LOG.logError( "Error closing result set: '" + e.getMessage() + "'.", e );
161                        }
162                    }
163                } finally {
164                    if ( stmt != null ) {
165                        try {
166                            stmt.close();
167                        } catch ( SQLException e ) {
168                            LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
169                        }
170                    }
171                }
172            }
173            return fids;
174        }
175    
176        /**
177         * Determines the feature ids that are matched by the given filter and that are either not
178         * locked or locked by the specified lockId.
179         * 
180         * @param ft
181         *            non-abstract feature type
182         * @param filter
183         *            constraints the feature instances
184         * @param lockId
185         *            optional id of associated lock (may be null)
186         * @return the feature ids that are matched by the given filter
187         * @throws DatastoreException
188         */
189        public List<FeatureId> determineAffectedAndModifiableFIDs( MappedFeatureType ft, Filter filter, String lockId )
190                                throws DatastoreException {
191    
192            List<FeatureId> affectedFids = determineAffectedFIDs( ft, filter );
193            List<FeatureId> modifiableFids = new ArrayList<FeatureId>( affectedFids.size() );
194            for ( FeatureId fid : affectedFids ) {
195                String lockedBy = LockManager.getInstance().getLockId( fid );
196                if ( lockedBy != null && !lockedBy.equals( lockId ) ) {
197                    String msg = Messages.getMessage( "DATASTORE_FEATURE_NOT_MODIFIABLE", fid, lockedBy );
198                    LOG.logInfo( msg );
199                } else {
200                    modifiableFids.add( fid );
201                }
202            }
203            return modifiableFids;
204        }
205    
206        /**
207         * Determines all complex properties and contained subfeature ids for a certain feature.
208         * 
209         * @param fid
210         *            id of the feature
211         * @return all complex properties and contained subfeature ids of the feature
212         * @throws DatastoreException
213         */
214        public Map<MappedFeaturePropertyType, List<FeatureId>> determineSubFeatures( FeatureId fid )
215                                throws DatastoreException {
216    
217            LOG.logDebug( "Determining sub features of feature '" + fid + "'..." );
218            Map<MappedFeaturePropertyType, List<FeatureId>> ptToSubFids = new HashMap<MappedFeaturePropertyType, List<FeatureId>>();
219            PropertyType[] properties = fid.getFeatureType().getProperties();
220            for ( PropertyType property : properties ) {
221                MappedPropertyType pt = (MappedPropertyType) property;
222                if ( pt instanceof MappedFeaturePropertyType ) {
223                    LOG.logDebug( "Complex property '" + pt.getName() + "'..." );
224                    MappedFeaturePropertyType fPt = (MappedFeaturePropertyType) pt;
225                    List<FeatureId> subFids = determineSubFIDs( fid, fPt );
226                    ptToSubFids.put( fPt, subFids );
227                }
228            }
229            return ptToSubFids;
230        }
231    
232        /**
233         * Determines the {@link FeatureId}s of the subfeatures contained in a specified feature's
234         * property.
235         * 
236         * @param fid
237         *            id of the feature (for which the subfeatures will be determined)
238         * @param pt
239         *            property type of the feature (that contains the subfeatures)
240         * @return the matched subfeature's ids (with concrete feature types)
241         * @throws DatastoreException
242         */
243        private List<FeatureId> determineSubFIDs( FeatureId fid, MappedFeaturePropertyType pt )
244                                throws DatastoreException {
245    
246            LOG.logDebug( "Determining sub feature ids for feature: " + fid + " and property " + pt.getName() );
247    
248            List<FeatureId> subFids = null;
249            MappedFeatureType containedFt = pt.getFeatureTypeReference().getFeatureType();
250            if ( containedFt.isAbstract() ) {
251                MappedFeatureType[] concreteFts = containedFt.getConcreteSubstitutions();
252                subFids = determineSubFIDs( fid, pt, concreteFts );
253            } else {
254                subFids = determineSubFIDs( fid, pt, containedFt );
255            }
256            return subFids;
257        }
258    
259        /**
260         * Determines all super features (as {@link FeatureId} instances) for a certain feature.
261         * 
262         * @param fid
263         *            id of the feature
264         * @return all super feature ids of the feature
265         * @throws DatastoreException
266         */
267        public Set<FeatureId> determineSuperFeatures( FeatureId fid )
268                                throws DatastoreException {
269    
270            LOG.logDebug( "Determining super features of feature " + fid.getAsString() );
271            Set<FeatureId> superFeatures = new HashSet<FeatureId>();
272            MappedFeatureType subFt = fid.getFeatureType();
273            Set<FeatureType> substitutableFts = subFt.getGMLSchema().getSubstitutables( subFt );
274            Set<MappedFeatureType> superFts = determineSuperFeatureTypes( substitutableFts );
275    
276            for ( MappedFeatureType superFt : superFts ) {
277                List<MappedFeaturePropertyType> featureProps = determineProperties( superFt, subFt );
278                for ( MappedFeaturePropertyType featureProp : featureProps ) {
279                    superFeatures.addAll( determineSuperFids( superFt, featureProp, fid ) );
280                }
281            }
282            return superFeatures;
283        }
284    
285        /**
286         * Determines all concrete feature types that can contain one or more of the given feature types
287         * inside a property.
288         * 
289         * @param subFts
290         * @return all concrete feature types that can contain the given feature type
291         */
292        private Set<MappedFeatureType> determineSuperFeatureTypes( Set<FeatureType> subFts ) {
293            Set<MappedFeatureType> superFts = new HashSet<MappedFeatureType>();
294            for ( FeatureType subFt : subFts ) {
295                superFts.addAll( determineSuperFeatureTypes( (MappedFeatureType) subFt ) );
296            }
297            return superFts;
298        }
299    
300        /**
301         * Determines all concrete feature types that can contain the given feature type inside a
302         * property.
303         * 
304         * @param subFt
305         * @return all concrete feature types that can contain the given feature type
306         */
307        private Set<MappedFeatureType> determineSuperFeatureTypes( MappedFeatureType subFt ) {
308            Set<MappedFeatureType> superFts = new HashSet<MappedFeatureType>();
309            MappedGMLSchema schema = subFt.getGMLSchema();
310            FeatureType[] fts = schema.getFeatureTypes();
311            for ( int i = 0; i < fts.length; i++ ) {
312                MappedFeatureType ft = (MappedFeatureType) fts[i];
313                if ( !ft.isAbstract() ) {
314                    PropertyType[] properties = ft.getProperties();
315                    for ( int j = 0; j < properties.length; j++ ) {
316                        MappedPropertyType property = (MappedPropertyType) properties[j];
317                        if ( property instanceof MappedFeaturePropertyType ) {
318                            MappedFeaturePropertyType ftProperty = (MappedFeaturePropertyType) property;
319                            if ( ftProperty.getFeatureTypeReference().getName().equals( subFt.getName() ) ) {
320                                superFts.add( ft );
321                            }
322                        }
323                    }
324                }
325    
326            }
327            return superFts;
328        }
329    
330        /**
331         * Determines all {@link MappedFeaturePropertyType} instances that the super feature type has
332         * and which contain features that may be substituted for features of the given sub feature
333         * type.
334         * 
335         * @param superFt
336         * @param subFt
337         * @return corresponding property types
338         */
339        private List<MappedFeaturePropertyType> determineProperties( MappedFeatureType superFt, MappedFeatureType subFt ) {
340            List<MappedFeaturePropertyType> featureProps = new ArrayList<MappedFeaturePropertyType>();
341            PropertyType[] properties = superFt.getProperties();
342            for ( PropertyType property : properties ) {
343                if ( property instanceof MappedFeaturePropertyType ) {
344                    MappedFeaturePropertyType featureProperty = (MappedFeaturePropertyType) property;
345                    MappedFeatureType containedFt = featureProperty.getFeatureTypeReference().getFeatureType();
346                    if ( subFt.getGMLSchema().isValidSubstitution( containedFt, subFt ) ) {
347                        featureProps.add( featureProperty );
348                    }
349                }
350            }
351            return featureProps;
352        }
353    
354        /**
355         * Determines all features (as {@link FeatureId}s) of the super feature type which contain the
356         * given feature instance in the also specified property.
357         * 
358         * @param superFt
359         * @param featureProp
360         * @param subFid
361         * @return corresponding <code>DeleteNodes</code>
362         */
363        private List<FeatureId> determineSuperFids( MappedFeatureType superFt, MappedFeaturePropertyType featureProp,
364                                                    FeatureId subFid )
365                                throws DatastoreException {
366            this.aliasGenerator.reset();
367            TableRelation[] relations = featureProp.getTableRelations();
368    
369            String superFtAlias = this.aliasGenerator.generateUniqueAlias();
370            String[] joinTableAliases = this.aliasGenerator.generateUniqueAliases( relations.length );
371            String subFtAlias = joinTableAliases[joinTableAliases.length - 1];
372    
373            StatementBuffer query = new StatementBuffer();
374            query.append( "SELECT DISTINCT " );
375            appendFeatureIdColumns( superFt, superFtAlias, query );
376            query.append( " FROM " );
377            query.append( superFt.getTable() );
378            query.append( " " );
379            query.append( superFtAlias );
380            String fromAlias = superFtAlias;
381            for ( int i = 0; i < relations.length; i++ ) {
382                String toAlias = joinTableAliases[i];
383                query.append( " JOIN " );
384                if ( i == relations.length - 1 ) {
385                    query.append( subFid.getFeatureType().getTable() );
386                } else {
387                    query.append( relations[i].getToTable() );
388                }
389                query.append( " " );
390                query.append( toAlias );
391                query.append( " ON " );
392                appendJoinCondition( relations[i], fromAlias, toAlias, query );
393                fromAlias = toAlias;
394            }
395    
396            query.append( " WHERE " );
397            MappedGMLId gmlId = subFid.getFidDefinition();
398            MappingField[] idFields = gmlId.getIdFields();
399            for ( int i = 0; i < idFields.length; i++ ) {
400                query.append( subFtAlias );
401                query.append( '.' );
402                query.append( idFields[i].getField() );
403                query.append( "=?" );
404                query.addArgument( subFid.getValue( i ), idFields[i].getType() );
405                if ( i != idFields.length - 1 ) {
406                    query.append( " AND " );
407                }
408            }
409    
410            List<FeatureId> fids = null;
411            PreparedStatement stmt = null;
412            ResultSet rs = null;
413            try {
414                stmt = this.datastore.prepareStatement( conn, query );
415                LOG.logDebug( "Performing: " + query );
416                rs = stmt.executeQuery();
417                fids = extractFeatureIds( rs, superFt );
418            } catch ( SQLException e ) {
419                LOG.logInfo( e.getMessage(), e );
420                throw new DatastoreException( "Error in determineSuperFeatures(): " + e.getMessage() );
421            } finally {
422                try {
423                    if ( rs != null ) {
424                        try {
425                            rs.close();
426                        } catch ( SQLException e ) {
427                            LOG.logError( "Error closing result set: '" + e.getMessage() + "'.", e );
428                        }
429                    }
430                } finally {
431                    if ( stmt != null ) {
432                        try {
433                            stmt.close();
434                        } catch ( SQLException e ) {
435                            LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
436                        }
437                    }
438                }
439            }
440            return fids;
441        }
442    
443        /**
444         * Determines the {@link FeatureId}s of the subfeatures contained in the given feature
445         * property.
446         * 
447         * @param fid
448         *            id of the feature
449         * @param pt
450         *            table relation from the feature table to the subfeature table
451         * @param concreteFt
452         *            concrete (non-abstract) type that is contained in the feature property
453         * @return the <code>FeatureId</code> or null (if there is no such subfeature)
454         * @throws DatastoreException
455         */
456        private List<FeatureId> determineSubFIDs( FeatureId fid, MappedFeaturePropertyType pt, MappedFeatureType concreteFt )
457                                throws DatastoreException {
458    
459            TableRelation[] relations = pt.getTableRelations();
460    
461            this.aliasGenerator.reset();
462            String[] aliases = this.aliasGenerator.generateUniqueAliases( relations.length + 1 );
463    
464            StatementBuffer query = new StatementBuffer();
465            query.append( "SELECT " );
466            appendFeatureIdColumns( concreteFt, aliases[aliases.length - 1], query );
467            query.append( " FROM " );
468            query.append( relations[0].getFromTable() );
469            query.append( " " );
470            query.append( aliases[0] );
471    
472            // append JOINs
473            String fromAlias = aliases[0];
474            for ( int i = 0; i < relations.length; i++ ) {
475                String toAlias = aliases[i + 1];
476                query.append( " JOIN " );
477                if ( i == relations.length - 1 ) {
478                    query.append( concreteFt.getTable() );
479                } else {
480                    query.append( relations[i].getToTable() );
481                }
482                query.append( " " );
483                query.append( toAlias );
484                query.append( " ON " );
485                appendJoinCondition( relations[i], fromAlias, toAlias, query );
486                fromAlias = toAlias;
487            }
488    
489            query.append( " WHERE " );
490            appendFeatureIdConstraint( query, fid, aliases[0] );
491    
492            List<FeatureId> subFids = null;
493            PreparedStatement stmt = null;
494            ResultSet rs = null;
495            try {
496                stmt = this.datastore.prepareStatement( conn, query );
497                LOG.logDebug( "Determining subfeature ids: " + query );
498                rs = stmt.executeQuery();
499                subFids = extractFeatureIds( rs, concreteFt );
500            } catch ( SQLException e ) {
501                LOG.logDebug( e.getMessage(), e );
502                throw new DatastoreException( "Error in #determineSubFIDs(): " + e.getMessage() );
503            } finally {
504                try {
505                    if ( rs != null ) {
506                        try {
507                            rs.close();
508                        } catch ( SQLException e ) {
509                            LOG.logError( "Error closing result set: '" + e.getMessage() + "'.", e );
510                        }
511                    }
512                } finally {
513                    if ( stmt != null ) {
514                        try {
515                            stmt.close();
516                        } catch ( SQLException e ) {
517                            LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
518                        }
519                    }
520                }
521            }
522            return subFids;
523        }
524    
525        /**
526         * Determines the feature ids of the subfeatures contained in the given feature property (that
527         * may contain features of different concrete types).
528         * 
529         * @param fid
530         *            id of the feature
531         * @param pt
532         *            complex property that contains the subfeatures
533         * @param concreteSubFts
534         *            all possible non-abstract feature types of the subfeatures
535         * @return the ids of the subfeatures
536         * @throws DatastoreException
537         */
538        private List<FeatureId> determineSubFIDs( FeatureId fid, MappedFeaturePropertyType pt,
539                                                  MappedFeatureType[] concreteSubFts )
540                                throws DatastoreException {
541    
542            List<FeatureId> subFids = null;
543    
544            TableRelation[] relations = pt.getTableRelations();
545            LOG.logDebug( "Determining sub feature ids for feature " + fid + ": relations.length: " + relations.length );
546    
547            switch ( relations.length ) {
548            case 1: {
549                // subfeature disambiguator in feature table (only zero or one subfeatures)
550                MappedFeatureType concreteSubFt = determineSubFt( fid, pt, concreteSubFts );
551                subFids = new ArrayList<FeatureId>( 1 );
552                if ( concreteSubFt != null ) {
553                    FeatureId subFid = determineSubFID( fid, relations[0], concreteSubFt );
554                    if ( subFid != null ) {
555                        subFids.add( subFid );
556                    }
557                }
558                break;
559            }
560            case 2: {
561                // subfeature disambiguator in join table (any number of subfeatures)
562                subFids = determineSubFIDs( fid, pt, concreteSubFts, relations );
563                break;
564            }
565            default: {
566                String msg = Messages.getMessage( "DATASTORE_SUBFT_TOO_MANY_RELATIONS", fid.getFeatureType().getName(),
567                                                  pt.getName() );
568                throw new DatastoreException( msg );
569            }
570            }
571            return subFids;
572        }
573    
574        /**
575         * Determine the concrete type of the subfeature that is stored in the specified property of a
576         * certain feature.
577         * <p>
578         * The relation to the sub feature table must be specified via a single step (join).
579         * 
580         * @param fid
581         *            id of the feature for which the concrete subfeature type is needed
582         * @param pt
583         *            property of the feature that contains the subfeature
584         * @param concreteSubFts
585         *            concrete types that may be contained in the property
586         * @return concrete type of the subfeature, or null if feature has no such property
587         * @throws DatastoreException
588         */
589        private MappedFeatureType determineSubFt( FeatureId fid, MappedFeaturePropertyType pt,
590                                                  MappedFeatureType[] concreteSubFts )
591                                throws DatastoreException {
592    
593            assert ( pt.getTableRelations().length == 1 );
594            TableRelation relation = pt.getTableRelations()[0];
595    
596            assert ( relation.getFromFields().length == 1 );
597            String fkColumn = relation.getFromFields()[0].getField();
598            String subFtColumn = FT_PREFIX + fkColumn;
599    
600            StatementBuffer query = new StatementBuffer();
601            query.append( "SELECT " );
602            query.append( subFtColumn );
603            query.append( " FROM " );
604            query.append( fid.getFeatureType().getTable() );
605            query.append( " WHERE " );
606            appendFeatureIdConstraint( query, fid );
607    
608            String localSubFtName = null;
609            PreparedStatement stmt = null;
610            ResultSet rs = null;
611            try {
612                stmt = this.datastore.prepareStatement( conn, query );
613                LOG.logDebug( "Determining concrete subfeature type: " + query );
614                rs = stmt.executeQuery();
615                rs.next();
616                localSubFtName = rs.getString( 1 );
617            } catch ( SQLException e ) {
618                LOG.logDebug( e.getMessage(), e );
619                throw new DatastoreException( "Error in determineConcreteSubFt() " + e.getMessage() );
620            } finally {
621                try {
622                    if ( rs != null ) {
623                        try {
624                            rs.close();
625                        } catch ( SQLException e ) {
626                            LOG.logError( "Error closing result set: '" + e.getMessage() + "'.", e );
627                        }
628                    }
629                } finally {
630                    if ( stmt != null ) {
631                        try {
632                            stmt.close();
633                        } catch ( SQLException e ) {
634                            LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
635                        }
636                    }
637                }
638            }
639    
640            MappedFeatureType concreteSubFt = null;
641    
642            if ( localSubFtName != null ) {
643                for ( MappedFeatureType type : concreteSubFts ) {
644                    if ( type.getName().getLocalName().equals( localSubFtName ) ) {
645                        concreteSubFt = fid.getFeatureType().getGMLSchema().getFeatureType( localSubFtName );
646                        break;
647                    }
648                }
649                if ( concreteSubFt == null ) {
650                    String msg = Messages.getMessage( "DATASTORE_FEATURE_TYPE_INFO_INCONSISTENT", pt.getName(), fid,
651                                                      subFtColumn, localSubFtName, pt.getFeatureTypeReference().getName() );
652                    throw new DatastoreException( msg );
653                }
654            }
655    
656            return concreteSubFt;
657        }
658    
659        /**
660         * Determines the {@link FeatureId} of the subfeature contained in the given feature property
661         * (if the feature has such a subfeature).
662         * 
663         * @param fid
664         *            id of the feature
665         * @param relation
666         *            table relation from the feature table to the subfeature table
667         * @param concreteFt
668         *            concrete (non-abstract) type that is contained in the feature property
669         * @return the <code>FeatureId</code> or null (if there is no such subfeature)
670         * @throws DatastoreException
671         */
672        private FeatureId determineSubFID( FeatureId fid, TableRelation relation, MappedFeatureType concreteFt )
673                                throws DatastoreException {
674    
675            this.aliasGenerator.reset();
676            String fromAlias = this.aliasGenerator.generateUniqueAlias();
677            String toAlias = this.aliasGenerator.generateUniqueAlias();
678    
679            StatementBuffer query = new StatementBuffer();
680            query.append( "SELECT " );
681            appendFeatureIdColumns( concreteFt, toAlias, query );
682            query.append( " FROM " );
683            query.append( relation.getFromTable() );
684            query.append( " " );
685            query.append( fromAlias );
686            query.append( " JOIN " );
687            query.append( concreteFt.getTable() );
688            query.append( " " );
689            query.append( toAlias );
690            query.append( " ON " );
691            appendJoinCondition( relation, fromAlias, toAlias, query );
692            query.append( " WHERE " );
693            appendFeatureIdConstraint( query, fid, fromAlias );
694    
695            FeatureId subFid = null;
696            PreparedStatement stmt = null;
697            ResultSet rs = null;
698            try {
699                stmt = this.datastore.prepareStatement( conn, query );
700                LOG.logDebug( "Determining subfeature id: " + query );
701                rs = stmt.executeQuery();
702                if ( rs.next() ) {
703                    subFid = extractFeatureId( rs, concreteFt );
704                }
705            } catch ( SQLException e ) {
706                LOG.logDebug( e.getMessage(), e );
707                throw new DatastoreException( "Error in #determineSubFID(): " + e.getMessage() );
708            } finally {
709                try {
710                    if ( rs != null ) {
711                        try {
712                            rs.close();
713                        } catch ( SQLException e ) {
714                            LOG.logError( "Error closing result set: '" + e.getMessage() + "'.", e );
715                        }
716                    }
717                } finally {
718                    if ( stmt != null ) {
719                        try {
720                            stmt.close();
721                        } catch ( SQLException e ) {
722                            LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
723                        }
724                    }
725                }
726            }
727            return subFid;
728        }
729    
730        /**
731         * Determines the feature ids of the subfeatures contained in the given feature property (that
732         * may contain features of different concrete types and is connected via a join table with
733         * feature type disambiguation column).
734         * 
735         * @param fid
736         * @param pt
737         * @return the matched subfeatures' ids
738         * @throws DatastoreException
739         */
740        private List<FeatureId> determineSubFIDs( FeatureId fid, MappedFeaturePropertyType pt,
741                                                  MappedFeatureType[] concreteSubFts, TableRelation[] relations )
742                                throws DatastoreException {
743            this.aliasGenerator.reset();
744            String fromAlias = this.aliasGenerator.generateUniqueAlias();
745            String jtAlias = this.aliasGenerator.generateUniqueAlias();
746    
747            StatementBuffer query = new StatementBuffer();
748            query.append( "SELECT " );
749    
750            // select feature type disambiguation column and from fields of second table relation
751            appendQualifiedColumn( query, jtAlias, FT_COLUMN );
752            MappingField[] fromFields = relations[1].getFromFields();
753            for ( int i = 0; i < fromFields.length; i++ ) {
754                query.append( ',' );
755                appendQualifiedColumn( query, jtAlias, fromFields[i].getField() );
756            }
757    
758            query.append( " FROM " );
759            query.append( relations[0].getFromTable() );
760            query.append( " " );
761            query.append( fromAlias );
762            query.append( " JOIN " );
763            query.append( relations[0].getToTable() );
764            query.append( " " );
765            query.append( jtAlias );
766            query.append( " ON " );
767            appendJoinCondition( relations[0], fromAlias, jtAlias, query );
768            query.append( " WHERE " );
769            appendFeatureIdConstraint( query, fid, fromAlias );
770    
771            List<FeatureId> subFids = new ArrayList<FeatureId>();
772            PreparedStatement stmt = null;
773            ResultSet rs = null;
774            try {
775                stmt = this.datastore.prepareStatement( conn, query );
776                LOG.logDebug( "Determining concrete subfeature types and join keys: " + query );
777                rs = stmt.executeQuery();
778                Object[] keyComponents = new Object[relations[1].getFromFields().length];
779                while ( rs.next() ) {
780                    String localSubFtName = rs.getString( 1 );
781                    for ( int i = 0; i < keyComponents.length; i++ ) {
782                        keyComponents[i] = rs.getObject( i + 2 );
783                    }
784                    MappedFeatureType concreteSubFt = null;
785                    for ( MappedFeatureType type : concreteSubFts ) {
786                        if ( type.getName().getLocalName().equals( localSubFtName ) ) {
787                            concreteSubFt = fid.getFeatureType().getGMLSchema().getFeatureType( localSubFtName );
788                            break;
789                        }
790                    }
791                    if ( concreteSubFt == null ) {
792                        String msg = Messages.getMessage( "DATASTORE_FEATURE_TYPE_INFO_INCONSISTENT", pt.getName(), fid,
793                                                          FT_COLUMN, localSubFtName, pt.getFeatureTypeReference().getName() );
794                        throw new DatastoreException( msg );
795                    }
796    
797                    subFids.add( determineSubFID( concreteSubFt, relations[1], keyComponents ) );
798                }
799            } catch ( SQLException e ) {
800                LOG.logDebug( e.getMessage(), e );
801                throw new DatastoreException( "Error in #determineSubFIDs(): " + e.getMessage() );
802            } finally {
803                try {
804                    if ( rs != null ) {
805                        try {
806                            rs.close();
807                        } catch ( SQLException e ) {
808                            LOG.logError( "Error closing result set: '" + e.getMessage() + "'.", e );
809                        }
810                    }
811                } finally {
812                    if ( stmt != null ) {
813                        try {
814                            stmt.close();
815                        } catch ( SQLException e ) {
816                            LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
817                        }
818                    }
819                }
820            }
821            return subFids;
822        }
823    
824        /**
825         * Determines the {@link FeatureId} of the subfeature referenced by the given
826         * {@link TableRelation}.
827         * 
828         * @param concreteFt
829         *            conrete (non-abstract) type that is contained in the feature property
830         * @param relation
831         *            table relation from the join table to the subfeature table
832         * @param keyComponents
833         * @return the <code>FeatureId</code> or null (if there is no such subfeature)
834         * @throws DatastoreException
835         */
836        private FeatureId determineSubFID( MappedFeatureType concreteSubFt, TableRelation relation, Object[] keyComponents )
837                                throws DatastoreException {
838            this.aliasGenerator.reset();
839            String fromAlias = this.aliasGenerator.generateUniqueAlias();
840            String toAlias = this.aliasGenerator.generateUniqueAlias();
841    
842            StatementBuffer query = new StatementBuffer();
843            query.append( "SELECT " );
844            appendFeatureIdColumns( concreteSubFt, toAlias, query );
845            query.append( " FROM " );
846            query.append( relation.getFromTable() );
847            query.append( " " );
848            query.append( fromAlias );
849            query.append( " JOIN " );
850            query.append( concreteSubFt.getTable() );
851            query.append( " " );
852            query.append( toAlias );
853            query.append( " ON " );
854            appendJoinCondition( relation, fromAlias, toAlias, query );
855            query.append( " WHERE " );
856            for ( int i = 0; i < keyComponents.length; i++ ) {
857                appendQualifiedColumn( query, fromAlias, relation.getFromFields()[i].getField() );
858                query.append( "=?" );
859                query.addArgument( keyComponents[i], relation.getFromFields()[i].getType() );
860                if ( i != keyComponents.length - 1 ) {
861                    query.append( " AND " );
862                }
863            }
864            FeatureId subFid = null;
865            PreparedStatement stmt = null;
866            ResultSet rs = null;
867            try {
868                stmt = this.datastore.prepareStatement( conn, query );
869                LOG.logDebug( "Determining subfeature id: " + query );
870                rs = stmt.executeQuery();
871                if ( rs.next() ) {
872                    subFid = extractFeatureId( rs, concreteSubFt );
873                }
874            } catch ( SQLException e ) {
875                LOG.logDebug( e.getMessage(), e );
876                throw new DatastoreException( "Error in #determineSubFID(): " + e.getMessage() );
877            } finally {
878                try {
879                    if ( rs != null ) {
880                        try {
881                            rs.close();
882                        } catch ( SQLException e ) {
883                            LOG.logError( "Error closing result set: '" + e.getMessage() + "'.", e );
884                        }
885                    }
886                } finally {
887                    if ( stmt != null ) {
888                        try {
889                            stmt.close();
890                        } catch ( SQLException e ) {
891                            LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
892                        }
893                    }
894                }
895            }
896            return subFid;
897        }
898    
899        /**
900         * Builds the initial SELECT statement that retrieves the feature ids that are matched by the
901         * given <code>WhereBuilder</code>.
902         * <p>
903         * The statement is structured like this:
904         * <ul>
905         * <li><code>SELECT</code></li>
906         * <li>comma-separated list of qualified fid fields</li>
907         * <li><code>FROM</code></li>
908         * <li>comma-separated list of tables and their aliases (this is needed to constrain the paths
909         * to selected XPath-PropertyNames)</li>
910         * <li><code>WHERE</code></li>
911         * <li>SQL representation of the Filter expression</li>
912         * </ul>
913         * 
914         * @param rootFt
915         * @param whereBuilder
916         * @return initial SELECT statement to retrieve the feature ids
917         * @throws DatastoreException
918         */
919        private StatementBuffer buildInitialFIDSelect( MappedFeatureType rootFt, WhereBuilder whereBuilder )
920                                throws DatastoreException {
921    
922            String tableAlias = whereBuilder.getRootTableAlias(0);
923            StatementBuffer query = new StatementBuffer();
924            query.append( "SELECT " );
925            appendFeatureIdColumns( rootFt, tableAlias, query );
926            query.append( " FROM " );
927            whereBuilder.appendJoinTableList( query );
928            whereBuilder.appendWhereCondition( query );
929            return query;
930        }
931    
932        /**
933         * Appends the alias qualified columns that make up the feature id to the given query.
934         * 
935         * @param featureType
936         * @param tableAlias
937         * @param query
938         */
939        protected void appendFeatureIdColumns( MappedFeatureType featureType, String tableAlias, StatementBuffer query ) {
940            MappingField[] fidFields = featureType.getGMLId().getIdFields();
941            for ( int i = 0; i < fidFields.length; i++ ) {
942                query.append( tableAlias );
943                query.append( '.' );
944                query.append( fidFields[i].getField() );
945                if ( i != fidFields.length - 1 ) {
946                    query.append( ',' );
947                }
948            }
949        }
950    
951        /**
952         * Extracts the {@FeatureId} in the current row of the given {@link ResultSet}.
953         * 
954         * @param rs
955         * @param ft
956         *            feature type (may not be abstract)
957         * @return feature ids
958         * @throws SQLException
959         * @throws DatastoreException
960         */
961        protected FeatureId extractFeatureId( ResultSet rs, MappedFeatureType ft )
962                                throws SQLException, DatastoreException {
963    
964            MappedGMLId gmlId = ft.getGMLId();
965            MappingField[] idFields = gmlId.getIdFields();
966    
967            Object[] idValues = new Object[idFields.length];
968            for ( int i = 0; i < idValues.length; i++ ) {
969                Object idValue = rs.getObject( i + 1 );
970                if ( idValue == null ) {
971                    String msg = Messages.getMessage( "DATASTORE_FEATURE_ID_NULL", ft.getTable(), ft.getName(),
972                                                      idFields[i].getField() );
973                    throw new DatastoreException( msg );
974                }
975                idValues[i] = idValue;
976            }
977    
978            return new FeatureId( ft, idValues );
979        }
980    
981        /**
982         * Extracts the feature ids in the given {@link ResultSet} as a {@List} of {@FeatureId}s.
983         * <p>
984         * If the given feature type is abstract, it is expected that the first column of the result set
985         * contains the local name of the feature type.
986         * 
987         * @param rs
988         * @param ft
989         *            feature type (may be abstract)
990         * @return feature ids
991         * @throws SQLException
992         * @throws DatastoreException
993         */
994        protected List<FeatureId> extractFeatureIds( ResultSet rs, MappedFeatureType ft )
995                                throws SQLException, DatastoreException {
996            List<FeatureId> featureIdList = new ArrayList<FeatureId>();
997            MappedGMLId gmlId = ft.getGMLId();
998            MappingField[] idFields = gmlId.getIdFields();
999            while ( rs.next() ) {
1000                int offset = 1;
1001                if ( ft.isAbstract() ) {
1002                    String localFtName = rs.getString( 1 );
1003                    ft = ft.getGMLSchema().getFeatureType( localFtName );
1004                    gmlId = ft.getGMLId();
1005                    idFields = gmlId.getIdFields();
1006                    offset = 2;
1007                }
1008                Object[] idValues = new Object[idFields.length];
1009                for ( int i = 0; i < idValues.length; i++ ) {
1010                    Object idValue = rs.getObject( i + offset );
1011                    if ( idValue == null ) {
1012                        String msg = Messages.getMessage( "DATASTORE_FEATURE_ID_NULL", ft.getTable(), ft.getName(),
1013                                                          idFields[i].getField() );
1014                        throw new DatastoreException( msg );
1015                    }
1016                    idValues[i] = idValue;
1017                }
1018                featureIdList.add( new FeatureId( ft, idValues ) );
1019            }
1020            return featureIdList;
1021        }
1022    
1023        protected void appendJoins( TableRelation[] tableRelation, String fromAlias, String[] toAliases,
1024                                    StatementBuffer query ) {
1025            for ( int i = 0; i < toAliases.length; i++ ) {
1026                String toAlias = toAliases[i];
1027                appendJoin( tableRelation[i], fromAlias, toAlias, query );
1028                fromAlias = toAlias;
1029            }
1030        }
1031    
1032        private void appendJoin( TableRelation tableRelation, String fromAlias, String toAlias, StatementBuffer query ) {
1033            query.append( " JOIN " );
1034            query.append( tableRelation.getToTable() );
1035            query.append( " " );
1036            query.append( toAlias );
1037            query.append( " ON " );
1038            appendJoinCondition( tableRelation, fromAlias, toAlias, query );
1039        }
1040    
1041        protected void appendJoinCondition( TableRelation tableRelation, String fromAlias, String toAlias,
1042                                            StatementBuffer query ) {
1043    
1044            MappingField[] fromFields = tableRelation.getFromFields();
1045            MappingField[] toFields = tableRelation.getToFields();
1046            for ( int i = 0; i < fromFields.length; i++ ) {
1047                query.append( toAlias );
1048                query.append( "." );
1049                query.append( toFields[i].getField() );
1050                query.append( "=" );
1051                query.append( fromAlias );
1052                query.append( "." );
1053                query.append( fromFields[i].getField() );
1054                if ( i != fromFields.length - 1 ) {
1055                    query.append( " AND " );
1056                }
1057            }
1058        }
1059    
1060        protected void appendFeatureIdConstraint( StatementBuffer query, FeatureId fid ) {
1061            MappingField[] idFields = fid.getFidDefinition().getIdFields();
1062            for ( int i = 0; i < idFields.length; i++ ) {
1063                query.append( idFields[i].getField() );
1064                query.append( "=?" );
1065                query.addArgument( fid.getValue( i ), idFields[i].getType() );
1066                if ( i < idFields.length - 1 ) {
1067                    query.append( " AND " );
1068                }
1069            }
1070        }
1071    
1072        protected void appendFeatureIdConstraint( StatementBuffer query, FeatureId fid, String tableAlias ) {
1073            MappingField[] idFields = fid.getFidDefinition().getIdFields();
1074            for ( int i = 0; i < idFields.length; i++ ) {
1075                query.append( tableAlias );
1076                query.append( '.' );
1077                query.append( idFields[i].getField() );
1078                query.append( "=?" );
1079                query.addArgument( fid.getValue( i ), idFields[i].getType() );
1080                if ( i < idFields.length - 1 ) {
1081                    query.append( " AND " );
1082                }
1083            }
1084        }
1085    
1086        /**
1087         * Appends the specified columns as a comma-separated list to the given query.
1088         * 
1089         * @param query
1090         *            StatementBuffer that the list is appended to
1091         * @param columns
1092         *            array of column names
1093         */
1094        public void appendColumnsList( StatementBuffer query, String[] columns ) {
1095            for ( int i = 0; i < columns.length; i++ ) {
1096                if ( columns[i].indexOf( '$' ) != -1 ) {
1097                    // function call
1098                    String column = columns[i];
1099                    column = column.replaceAll( "\\$\\.", "" );
1100                    query.append( column );
1101    
1102                } else {
1103                    query.append( columns[i] );
1104                }
1105    
1106                if ( i != columns.length - 1 ) {
1107                    query.append( ',' );
1108                }
1109            }
1110        }
1111    
1112        /**
1113         * Appends the specified columns as alias-qualified, comma-separated list to the given query.
1114         * 
1115         * @param query
1116         *            StatementBuffer that the list is appended to
1117         * @param tableAlias
1118         *            alias to use as qualifier (alias.field)
1119         * @param columns
1120         *            array of column names
1121         */
1122        public void appendQualifiedColumnsList( StatementBuffer query, String tableAlias, String[] columns ) {
1123            for ( int i = 0; i < columns.length; i++ ) {
1124                appendQualifiedColumn( query, tableAlias, columns[i] );
1125                if ( i != columns.length - 1 ) {
1126                    query.append( ',' );
1127                }
1128            }
1129        }
1130    
1131        /**
1132         * Appends the specified column to the given query.
1133         * 
1134         * @param query
1135         *            StatementBuffer that the list is appended to
1136         * @param tableAlias
1137         *            alias to use as qualifier (alias.field)
1138         * @param column
1139         *            column name
1140         */
1141        public void appendQualifiedColumn( StatementBuffer query, String tableAlias, String column ) {
1142            query.append( tableAlias );
1143            query.append( '.' );
1144            query.append( column );
1145        }
1146    }