    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/io/datastore/sde/SDEQueryHandler.java $
    002    /*----------------    FILE HEADER  ------------------------------------------
    003
    004     This file is part of deegree.
    005     Copyright (C) 2006 by: M.O.S.S. Computer Grafik Systeme GmbH
    006     Hohenbrunner Weg 13
    007     D-82024 Taufkirchen
    008     http://www.moss.de/
    009
    010     This library is free software; you can redistribute it and/or
    011     modify it under the terms of the GNU Lesser General Public
    012     License as published by the Free Software Foundation; either
    013     version 2.1 of the License, or (at your option) any later version.
    014
    015     This library is distributed in the hope that it will be useful,
    016     but WITHOUT ANY WARRANTY; without even the implied warranty of
    017     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    018     Lesser General Public License for more details.
    019
    020     You should have received a copy of the GNU Lesser General Public
    021     License along with this library; if not, write to the Free Software
    022     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    023
    024     ---------------------------------------------------------------------------*/
    025    package org.deegree.io.datastore.sde;
    026
    027    import java.util.ArrayList;
    028    import java.util.Collection;
    029    import java.util.HashMap;
    030    import java.util.HashSet;
    031    import java.util.Iterator;
    032    import java.util.List;
    033    import java.util.Map;
    034    import java.util.Set;
    035
    036    import org.deegree.datatypes.QualifiedName;
    037    import org.deegree.framework.log.ILogger;
    038    import org.deegree.framework.log.LoggerFactory;
    039    import org.deegree.io.datastore.DatastoreException;
    040    import org.deegree.io.datastore.FeatureId;
    041    import org.deegree.io.datastore.PropertyPathResolver;
    042    import org.deegree.io.datastore.PropertyPathResolvingException;
    043    import org.deegree.io.datastore.schema.MappedFeatureType;
    044    import org.deegree.io.datastore.schema.MappedGeometryPropertyType;
    045    import org.deegree.io.datastore.schema.MappedPropertyType;
    046    import org.deegree.io.datastore.schema.MappedSimplePropertyType;
    047    import org.deegree.io.datastore.schema.TableRelation;
    048    import org.deegree.io.datastore.schema.content.MappingField;
    049    import org.deegree.io.datastore.schema.content.SimpleContent;
    050    import org.deegree.io.datastore.sql.TableAliasGenerator;
    051    import org.deegree.io.sdeapi.SDEConnection;
    052    import org.deegree.model.feature.Feature;
    053    import org.deegree.model.feature.FeatureCollection;
    054    import org.deegree.model.feature.FeatureFactory;
    055    import org.deegree.model.feature.FeatureProperty;
    056    import org.deegree.model.feature.XLinkedFeatureProperty;
    057    import org.deegree.model.filterencoding.ComplexFilter;
    058    import org.deegree.model.spatialschema.Geometry;
    059    import org.deegree.ogcbase.PropertyPath;
    060    import org.deegree.ogcwebservices.wfs.operation.Query;
    061    import org.deegree.ogcwebservices.wfs.operation.GetFeature.RESULT_TYPE;
    062
    063    import com.esri.sde.sdk.client.SeFilter;
    064    import com.esri.sde.sdk.client.SeLayer;
    065    import com.esri.sde.sdk.client.SeObjectId;
    066    import com.esri.sde.sdk.client.SeQuery;
    067    import com.esri.sde.sdk.client.SeRow;
    068    import com.esri.sde.sdk.client.SeSqlConstruct;
    069    import com.esri.sde.sdk.client.SeState;
    070
    071    /**
    072     * Special <code>QueryHandler</code> implementation for the <code>SDEDatastore</code>.
    073     *
    074     * @author <a href="mailto:cpollmann@moss.de">Christoph Pollmann</a>
    075     * @author last edited by: $Author: mschneider $
    076     *
    077     * @version $Revision: 23718 $
    078     */
    079    public class SDEQueryHandler extends AbstractSDERequestHandler {
    080
    081        private static final ILogger LOG = LoggerFactory.getLogger( SDEQueryHandler.class );
    082
    083        /**
    084         *  requested feature type
    085         */
    086        protected MappedFeatureType rootFeatureType;
    087
    088        /**
    089         *  requested properties of the feature type
    090         */
    091        protected PropertyPath[] propertyNames;
    092
    093        /**
    094         *  used to build the initial SELECT (especially the WHERE-clause)
    095         */
    096        protected SDEWhereBuilder whereBuilder;
    097
    098        /**
    099         * key: feature id of features that are generated or are in generation
    100         */
    101        protected Set<FeatureId> featuresInGeneration = new HashSet<FeatureId>();
    102
    103        /**
    104         *  key: feature id value, value: Feature
    105         */
    106        protected Map<String, Feature> featureMap = new HashMap<String, Feature>( 1000 );
    107
    108        /**
    109         *  value: XLinkedFeatureProperty
    110         */
    111        private Collection<XLinkedFeatureProperty> xlinkProperties = new ArrayList<XLinkedFeatureProperty>();
    112
    113        private Query query = null;
    114
    115        /**
    116         * Creates a new instance of <code>SDEQueryHandler</code> from the given parameters.
    117         *
    118         * @param datastore
    119         *            datastore that spawned this QueryHandler
    120         * @param aliasGenerator
    121         *            used to generate unique aliases for the tables in the SELECT statements
    122         * @param conn
    123         *            SDEConnection to execute the generated SELECT statements against
    124         * @param rootFts
    125         *            queried feature types
    126         * @param query
    127         *            Query to perform
    128         * @throws DatastoreException
    129         */
    130        public SDEQueryHandler( SDEDatastore datastore, TableAliasGenerator aliasGenerator, SDEConnection conn,
    131                                MappedFeatureType[] rootFts, Query query ) throws DatastoreException {
    132            super( datastore, aliasGenerator, conn );
    133            this.rootFeatureType = rootFts[0];
    134            this.propertyNames = PropertyPathResolver.normalizePropertyPaths( rootFeatureType, null,
    135                                                                              query.getPropertyNames() );
    136            this.whereBuilder = this.datastore.getWhereBuilder( rootFts, query.getAliases(), query.getFilter(),
    137                                                                aliasGenerator );
    138            this.aliasGenerator = aliasGenerator;
    139            this.query = query;
    140        }
    141
    142        /**
    143         * Performs the associated <code>Query</code> against the datastore.
    144         *
    145         * @return collection of requested features
    146         * @throws DatastoreException
    147         */
    148        public FeatureCollection performQuery()
    149                                throws DatastoreException {
    150
    151            FeatureCollection result = null;
    152            if ( this.query.getResultType() == RESULT_TYPE.HITS ) {
    153                // TODO
    154            } else {
    155                result = performContentQuery();
    156            }
    157
    158            return result;
    159        }
    160
    161        /**
    162         * Performs a query for the feature instances that match the query constraints. This corresponds to a query with
    163         * resultType=RESULTS.
    164         *
    165         * @return a feature collection containing the features that match the query constraints
    166         * @throws PropertyPathResolvingException
    167         * @throws DatastoreException
    168         */
    169        private FeatureCollection performContentQuery()
    170                                throws PropertyPathResolvingException, DatastoreException {
    171
    172            FeatureCollection result = FeatureFactory.createFeatureCollection( "ID", 10000 );
    173            String[] columns;
    174            // TODO respect alias
    175            Map<MappedPropertyType, Collection<PropertyPath>> requestedPropertyMap = PropertyPathResolver.determineFetchProperties(
    176                                                                                                                                    this.rootFeatureType,
    177                                                                                                                                    null,
    178                                                                                                                                    this.propertyNames );
    179            MappedPropertyType[] requestedProperties = new MappedPropertyType[requestedPropertyMap.size()];
    180            requestedProperties = requestedPropertyMap.keySet().toArray( requestedProperties );
    181
    182            Map<String, List<MappingField>> columnsMap = buildColumnsMap( this.rootFeatureType, requestedProperties, true );
    183            columns = columnsMap.keySet().toArray( new String[columnsMap.size()] );
    184            Map<MappingField,Integer> mappingFieldsMap = buildMappingFieldMap( columns, columnsMap );
    185
    186            SeQuery stmt = buildInitialSelect( columns );
    187            Object[] resultValues = new Object[columns.length];
    188
    189            // necessary to handle that a feature may occur several times in result set
    190            Set<FeatureId> rootFeatureIds = new HashSet<FeatureId>();
    191
    192            try {
    193                int maxFeatures = this.query.getMaxFeatures();
    194                int startPosition = this.query.getStartPosition();
    195                int rowCount = 0;
    196                stmt.execute();
    197                SeRow row = null;
    198                if ( maxFeatures != -1 ) {
    199                    if ( startPosition < 0 ) {
    200                        startPosition = 0;
    201                    }
    202                }
    203                for ( ;; ) {
    204                    try {
    205                        row = stmt.fetch();
    206                    } catch ( Exception e ) {
    207                        row = null;
    208                    }
    209                    if ( null == row )
    210                        break;
    211                    rowCount++;
    212                    if ( rowCount < startPosition )
    213                        continue;
    214                    // collect result values
    215                    for ( int i = 0; i < resultValues.length; i++ ) {
    216                        try {
    217                            resultValues[i] = row.getObject( i );
    218                        } catch ( Exception e ) {
    219                            //never happens???
    220                        }
    221                    }
    222                    FeatureId fid = extractFeatureId( this.rootFeatureType, mappingFieldsMap, resultValues );
    223
    224                    // skip it if this root feature has already been fetched
    225                    if ( !rootFeatureIds.contains( fid ) ) {
    226
    227                        // feature also may have been fetched already as subfeature
    228                        Feature feature = this.featureMap.get( fid );
    229                        if ( feature == null ) {
    230                            feature = extractFeature( fid, this.rootFeatureType, requestedPropertyMap, mappingFieldsMap,
    231                                                      resultValues );
    232                        }
    233                        result.add( feature );
    234                    }
    235                }
    236            } catch( Exception e ){
    237                throw new DatastoreException( e );
    238            }finally {
    239                try {
    240                    if ( stmt != null ) {
    241                        stmt.close();
    242                    }
    243                } catch ( Exception exc2 ) {
    244                    throw new DatastoreException( exc2 );
    245                }
    246            }
    247            resolveXLinks();
    248            result.setAttribute( "numberOfFeatures", "" + result.size() );
    249            return result;
    250        }
    251
    252        /**
    253         * @throws DatastoreException
    254         */
    255        protected void resolveXLinks()
    256                                throws DatastoreException {
    257            for( XLinkedFeatureProperty property : this.xlinkProperties ){
    258                Feature feature = this.featureMap.get( property.getTargetFeatureId() );
    259                if ( feature == null ) {
    260                    throw new DatastoreException( "Internal error in QueryHandler." );
    261                }
    262                property.setValue( feature );
    263            }
    264        }
    265
    266        /**
    267         * Builds the initial SELECT statement.
    268         * <p>
    269         * This statement determines all feature ids that are affected by the filter, but also SELECTs all properties that
    270         * are stored in the root feature type's table (to improve efficiency).
    271         * </p>
    272         * <p>
    273         * The statement is structured like this:
    274         * <ul>
    275         * <li><code>SELECT</code></li>
    276         * <li>comma-separated list of selected qualified fields</li>
    277         * <li><code>FROM</code></li>
    278         * <li>comma-separated list of tables and their aliases (this is needed to constrain the paths to selected
    279         * XPath-PropertyNames)</li>
    280         * <li><code>WHERE</code></li>
    281         * <li>SQL representation of the Filter expression</li>
    282         * <li><code>ORDER BY</code></li>
    283         * <li>qualified sort criteria fields</li>
    284         * </ul>
    285         * </p>
    286         *
    287         * @param columns
    288         * @return the initial query
    289         */
    290        protected SeQuery buildInitialSelect( String[] columns ) {
    291            SeQuery query = null;
    292            try {
    293                StringBuffer whereCondition = new StringBuffer();
    294                whereBuilder.appendWhereCondition( whereCondition );
    295                SeSqlConstruct constr = new SeSqlConstruct( rootFeatureType.getTable(), whereCondition.toString() );
    296                query = new SeQuery( getConnection().getConnection(), columns, constr );
    297                query.setState( getConnection().getState().getId(), new SeObjectId( SeState.SE_NULL_STATE_ID ),
    298                                SeState.SE_STATE_DIFF_NOCHECK );
    299
    300                query.prepareQuery();
    301
    302                if ( this.query.getFilter() instanceof ComplexFilter ) {
    303                    // There is NO chance, to make a new SeCoordinateReference equal to the existing crs
    304                    // of the requested layer.
    305                    // So, we give it a chance, by passing the layer definitions (and its associated
    306                    // crs) to the whereBuilder method
    307                    List<SeLayer> layers = getConnection().getConnection().getLayers();
    308                    SeFilter[] spatialFilter = whereBuilder.buildSpatialFilter( (ComplexFilter) this.query.getFilter(),
    309                                                                                layers );
    310                    if ( null != spatialFilter && 0 < spatialFilter.length ) {
    311                        query.setSpatialConstraints( SeQuery.SE_OPTIMIZE, false, spatialFilter );
    312                    }
    313                }
    314            } catch ( Exception e ) {
    315                e.printStackTrace();
    316            }
    317
    318            // append sort criteria (simple implementation)
    319            // TODO implement sort as comparison operator in feature collection
    320            // because fetching of subfeatures can not be handled here
    321            // sort only provides ascendig sorting at the moment!
    322            /*
    323             * TODO!!!! SortProperty[] sortProperties = query.getSortProperties(); if (null != sortProperties && 0 <
    324             * sortProperties.length) { String[] sortColumns = new String[sortProperties.length]; for ( int i = 0; i <
    325             * sortProperties.length; i++ ) { PropertyPath pp = sortProperties[i].getSortProperty(); PropertyPath npp =
    326             * PropertyPathResolver.normalizePropertyPath( rootFeatureType, pp ); QualifiedName propertyName = npp.getStep(
    327             * 1 ).getPropertyName(); PropertyType property = rootFeatureType.getProperty( propertyName ); PropertyContent[]
    328             * contents = ( (MappedPropertyType) property ).getContents(); if ( contents[0] instanceof SimplePropertyContent ) {
    329             * sortColumns[i] = ( (SimplePropertyContent) contents[0] ).getMappingField().getField(); } } querybuf.append("
    330             * ORDER BY "); appendColumnsList( querybuf, sortColumns ); }
    331             */
    332            return query;
    333        }
    334
    335        /**
    336         * Builds a SELECT statement to fetch features / properties that are stored in a related table.
    337         *
    338         * @param columns
    339         *            table column names to fetch
    340         * @param relations
    341         *            table relations that lead to the table where the property is stored
    342         * @param resultValues
    343         *            all retrieved columns from one result set row
    344         * @param mappingFieldMap
    345         *            key class: MappingField, value class: Integer (this is the associated index in resultValues)
    346         * @return the statement or null if the keys in resultValues contain NULL values
    347         */
    348        private SeQuery buildSubsequentSelect( String[] columns, TableRelation[] relations, Object[] resultValues,
    349                                               Map<MappingField, Integer> mappingFieldMap ) {
    350            SeQuery query = null;
    351            try {
    352                StringBuffer whereCondition = new StringBuffer();
    353
    354                // joins can't be used in versioned SDEs (so only one join level possible)
    355
    356                // append key constraints
    357                MappingField[] fromFields = relations[0].getFromFields();
    358                MappingField[] toFields = relations[0].getToFields();
    359                for ( int i = 0; i < fromFields.length; i++ ) {
    360                    Integer resultPos = mappingFieldMap.get( fromFields[i] );
    361                    Object keyValue = resultValues[resultPos.intValue()];
    362                    if ( keyValue == null ) {
    363                        return null;
    364                    }
    365                    whereCondition.append( toFields[i].getField() );
    366                    whereCondition.append( "='" + keyValue.toString() + "'" );
    367                    if ( i != fromFields.length - 1 ) {
    368                        whereCondition.append( " AND " );
    369                    }
    370                }
    371                SeSqlConstruct constr = new SeSqlConstruct( relations[0].getToTable(), whereCondition.toString() );
    372
    373                query = new SeQuery( getConnection().getConnection(), columns, constr );
    374                query.setState( getConnection().getState().getId(), new SeObjectId( SeState.SE_NULL_STATE_ID ),
    375                                SeState.SE_STATE_DIFF_NOCHECK );
    376                query.prepareQuery();
    377            } catch ( Exception e ) {
    378                e.printStackTrace();
    379            }
    380            return query;
    381        }
    382
    383        /**
    384         * Extracts a feature from the values of a result set row.
    385         *
    386         * @param fid
    387         *            feature id of the feature
    388         * @param featureType
    389         *            feature type of the feature to be extracted
    390         * @param requestedPropertyMap
    391         *            requested <code>MappedPropertyType</code>s mapped to <code>Collection</code> of
    392         *            <code>PropertyPath</code>s
    393         * @param mappingFieldsMap
    394         *            key class: MappingField, value class: Integer (this is the associated index in resultValues)
    395         * @param resultValues
    396         *            all retrieved columns from one result set row
    397         * @return the extracted feature
    398         * @throws DatastoreException
    399         */
    400        protected Feature extractFeature( FeatureId fid, MappedFeatureType featureType,
    401                                          Map<MappedPropertyType, Collection<PropertyPath>> requestedPropertyMap,
    402                                          Map<MappingField,Integer> mappingFieldsMap, Object[] resultValues )
    403                                throws DatastoreException {
    404
    405            this.featuresInGeneration.add( fid );
    406
    407            // extract the requested properties of the feature
    408            List<FeatureProperty> propertyList = new ArrayList<FeatureProperty>();
    409            Iterator<MappedPropertyType> propertyIter = requestedPropertyMap.keySet().iterator();
    410            while ( propertyIter.hasNext() ) {
    411                MappedPropertyType requestedProperty = propertyIter.next();
    412                // PropertyPath[] requestingPaths = PropertyPathResolver.determineSubPropertyPaths
    413                // (requestedProperty, propertyPaths);
    414                Collection<FeatureProperty> props = extractProperties( requestedProperty, mappingFieldsMap, resultValues );
    415                propertyList.addAll( props );
    416            }
    417            FeatureProperty[] properties = propertyList.toArray( new FeatureProperty[propertyList.size()] );
    418            Feature feature = FeatureFactory.createFeature( fid.getAsString(), featureType, properties );
    419
    420            this.featureMap.put( fid.getAsString(), feature );
    421            return feature;
    422        }
    423
    424        /**
    425         * Extracts the feature id from the values of a result set row.
    426         *
    427         * @param ft
    428         *            feature type for which the id shall be extracted
    429         * @param mappingFieldMap
    430         *            key class: MappingField, value class: Integer (this is the associated index in resultValues)
    431         * @param resultValues
    432         *            all retrieved columns from one result set row
    433         * @return the feature id
    434         */
    435        protected FeatureId extractFeatureId( MappedFeatureType ft, Map<MappingField, Integer> mappingFieldMap, Object[] resultValues ) {
    436            MappingField[] idFields = ft.getGMLId().getIdFields();
    437            Object[] idValues = new Object[idFields.length];
    438            for ( int i = 0; i < idFields.length; i++ ) {
    439                Integer resultPos = mappingFieldMap.get( idFields[i] );
    440                idValues[i] = resultValues[resultPos.intValue()];
    441            }
    442            return new FeatureId( ft, idValues );
    443        }
    444
    445        /**
    446         * Extracts the properties of the given property type from the values of a result set row. If the property is stored
    447         * in related table, only the key values are present in the result set row and more SELECTs are built and executed
    448         * to build the property.
    449         * <p>
    450         * FYI: If the property is not stored in a related table, only one FeatureProperty is built, otherwise the number of
    451         * properties depends on the multiplicity of the relation.
    452         *
    453         * @param propertyType
    454         *            the mapped property type to be extracted
    455         * @param mappingFieldMap
    456         *            key class: MappingField, value class: Integer (this is the associated index in resultValues)
    457         * @param resultValues
    458         *            all retrieved columns from one result set row
    459         * @return Collection of FeatureProperty instances
    460         *
    461         * @throws DatastoreException
    462         */
    463        private Collection<FeatureProperty> extractProperties( MappedPropertyType propertyType,
    464                                                               Map<MappingField, Integer> mappingFieldMap,
    465                                                               Object[] resultValues )
    466                                throws DatastoreException {
    467            Collection<FeatureProperty> result = null;
    468
    469            TableRelation[] tableRelations = propertyType.getTableRelations();
    470            if ( tableRelations != null && tableRelations.length != 0 ) {
    471                LOG.logDebug( "Fetching related properties: '" + propertyType.getName() + "'..." );
    472                result = fetchRelatedProperties( propertyType.getName(), propertyType, mappingFieldMap, resultValues );
    473            } else {
    474                Object propertyValue = null;
    475                if ( propertyType instanceof MappedSimplePropertyType ) {
    476                    SimpleContent content = ( (MappedSimplePropertyType) propertyType ).getContent();
    477                    if ( content instanceof MappingField ) {
    478                        MappingField field = (MappingField) content;
    479                        Integer resultPos = mappingFieldMap.get( field );
    480                        propertyValue = resultValues[resultPos.intValue()];
    481                    }
    482                } else if ( propertyType instanceof MappedGeometryPropertyType ) {
    483                    MappedGeometryPropertyType geomPropertyType = (MappedGeometryPropertyType) propertyType;
    484                    MappingField field = geomPropertyType.getMappingField();
    485                    Integer resultPos = mappingFieldMap.get( field );
    486                    propertyValue = resultValues[resultPos.intValue()];
    487                    propertyValue = this.datastore.convertDBToDegreeGeometry( propertyValue, geomPropertyType.getCS() );
    488                } else {
    489                    String msg = "Unsupported property type: '" + propertyType.getClass().getName()
    490                                 + "' in QueryHandler.extractProperties(). ";
    491                    LOG.logError( msg );
    492                    throw new IllegalArgumentException( msg );
    493                }
    494                FeatureProperty property = FeatureFactory.createFeatureProperty( propertyType.getName(), propertyValue );
    495                result = new ArrayList<FeatureProperty>();
    496                result.add( property );
    497            }
    498            return result;
    499        }
    500
    501        /**
    502         *
    503         * @param propertyName
    504         * @param pt
    505         * @param mappingFieldMap
    506         *            key class: MappingField, value class: Integer (this is the associated index in resultValues)
    507         * @param resultValues
    508         *            all retrieved columns from one result set row
    509         * @return Collection of FeatureProperty instances
    510         * @throws DatastoreException
    511         */
    512        private Collection<FeatureProperty> fetchRelatedProperties( QualifiedName propertyName, MappedPropertyType pt,
    513                                                                    Map<MappingField, Integer> mappingFieldMap, Object[] resultValues ) throws DatastoreException {
    514            Collection<FeatureProperty> result = new ArrayList<FeatureProperty>( 100 );
    515            SeQuery stmt = null;
    516            SeRow row = null;
    517            try {
    518                if ( pt instanceof MappedSimplePropertyType ) {
    519
    520                    SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent();
    521                    if ( content instanceof MappingField ) {
    522                        String column = ( (MappingField) content ).getField();
    523                        stmt = buildSubsequentSelect( new String[] { column }, pt.getTableRelations(), resultValues,
    524                                                      mappingFieldMap );
    525                        if ( stmt != null ) {
    526                            stmt.execute();
    527                            for ( ;; ) {
    528                                try {
    529                                    row = stmt.fetch();
    530                                } catch ( Exception e ) {
    531                                    row = null;
    532                                }
    533                                if ( null == row )
    534                                    break;
    535                                Object propertyValue = row.getObject( 0 );
    536                                FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName,
    537                                                                                                 propertyValue );
    538                                result.add( property );
    539                            }
    540                        }
    541                    }
    542                } else if ( pt instanceof MappedGeometryPropertyType ) {
    543                    MappedGeometryPropertyType geomPropertyType = (MappedGeometryPropertyType) pt;
    544                    String column = geomPropertyType.getMappingField().getField();
    545
    546                    stmt = buildSubsequentSelect( new String[] { column }, pt.getTableRelations(), resultValues,
    547                                                  mappingFieldMap );
    548                    if ( stmt != null ) {
    549                        stmt.execute();
    550                        for ( ;; ) {
    551                            try {
    552                                row = stmt.fetch();
    553                            } catch ( Exception e ) {
    554                                row = null;
    555                            }
    556                            if ( null == row )
    557                                break;
    558                            Object value = row.getObject( 0 );
    559                            Geometry geometry = this.datastore.convertDBToDegreeGeometry( value, geomPropertyType.getCS() );
    560                            FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, geometry );
    561                            result.add( property );
    562                        }
    563                    }
    564                } else {
    565                    String msg = "Unsupported content type: '" + pt.getClass().getName()
    566                                 + "' in QueryHandler.fetchRelatedProperties().";
    567                    LOG.logError( msg );
    568                    throw new DatastoreException( msg );
    569                }
    570            } catch ( Exception exc ) {
    571                    throw new DatastoreException( exc );
    572            } finally {
    573                try {
    574                    if ( stmt != null ) {
    575                        stmt.close();
    576                    }
    577                } catch ( Exception exc2 ) {
    578                    throw new DatastoreException( exc2 );
    579                }
    580            }
    581            return result;
    582        }
    583    }