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 }