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