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