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 }