001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/io/datastore/sql/FeatureFetcher.java $ 002 /*---------------- FILE HEADER ------------------------------------------ 003 004 This file is part of deegree. 005 Copyright (C) 2001-2007 by: 006 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.Collection; 051 import java.util.HashMap; 052 import java.util.HashSet; 053 import java.util.List; 054 import java.util.Map; 055 import java.util.Set; 056 057 import org.deegree.datatypes.QualifiedName; 058 import org.deegree.framework.log.ILogger; 059 import org.deegree.framework.log.LoggerFactory; 060 import org.deegree.framework.util.StringTools; 061 import org.deegree.i18n.Messages; 062 import org.deegree.io.datastore.DatastoreException; 063 import org.deegree.io.datastore.FeatureId; 064 import org.deegree.io.datastore.PropertyPathResolver; 065 import org.deegree.io.datastore.schema.MappedFeaturePropertyType; 066 import org.deegree.io.datastore.schema.MappedFeatureType; 067 import org.deegree.io.datastore.schema.MappedGMLId; 068 import org.deegree.io.datastore.schema.MappedGeometryPropertyType; 069 import org.deegree.io.datastore.schema.MappedPropertyType; 070 import org.deegree.io.datastore.schema.MappedSimplePropertyType; 071 import org.deegree.io.datastore.schema.TableRelation; 072 import org.deegree.io.datastore.schema.content.ConstantContent; 073 import org.deegree.io.datastore.schema.content.MappingField; 074 import org.deegree.io.datastore.schema.content.MappingGeometryField; 075 import org.deegree.io.datastore.schema.content.SQLFunctionCall; 076 import org.deegree.io.datastore.schema.content.SimpleContent; 077 import org.deegree.model.crs.CRSFactory; 078 import org.deegree.model.crs.CoordinateSystem; 079 import org.deegree.model.crs.UnknownCRSException; 080 import org.deegree.model.feature.Feature; 081 import org.deegree.model.feature.FeatureFactory; 082 import org.deegree.model.feature.FeatureProperty; 083 import org.deegree.model.feature.schema.PropertyType; 084 import org.deegree.model.spatialschema.Geometry; 085 import org.deegree.ogcbase.PropertyPath; 086 import org.deegree.ogcwebservices.wfs.operation.Query; 087 088 /** 089 * The only implementation of this abstract class is the {@link QueryHandler} class. 090 * <p> 091 * While the {@link QueryHandler} class performs the initial SELECT, {@link FeatureFetcher} is responsible for any 092 * subsequent SELECTs that may be necessary. 093 * 094 * @see QueryHandler 095 * 096 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a> 097 * @author last edited by: $Author: mschneider $ 098 * 099 * @version $Revision: 7782 $, $Date: 2007-07-17 18:40:42 +0200 (Di, 17 Jul 2007) $ 100 */ 101 abstract class FeatureFetcher extends AbstractRequestHandler { 102 103 private static final ILogger LOG = LoggerFactory.getLogger( FeatureFetcher.class ); 104 105 // key: feature id of features that are generated or are in generation 106 protected Set<FeatureId> featuresInGeneration = new HashSet<FeatureId>(); 107 108 // key: feature id value, value: Feature 109 protected Map<FeatureId, Feature> featureMap = new HashMap<FeatureId, Feature>( 1000 ); 110 111 // key: feature id value, value: property instances that contain the feature 112 protected Map<FeatureId, List<FeatureProperty>> fidToPropertyMap = new HashMap<FeatureId, List<FeatureProperty>>(); 113 114 // provides virtual content (constants, sql functions, ...) 115 protected VirtualContentProvider vcProvider; 116 117 protected Query query; 118 119 private CoordinateSystem queryCS; 120 121 // key: geometry field, value: function call that transforms it to the queried CS 122 private Map<MappingGeometryField, SQLFunctionCall> fieldToTransformCall = new HashMap<MappingGeometryField, SQLFunctionCall>(); 123 124 FeatureFetcher( AbstractSQLDatastore datastore, TableAliasGenerator aliasGenerator, Connection conn, Query query ) 125 throws DatastoreException { 126 super( datastore, aliasGenerator, conn ); 127 this.query = query; 128 if ( this.query.getSrsName() != null ) { 129 try { 130 this.queryCS = CRSFactory.create( this.query.getSrsName() ); 131 } catch ( UnknownCRSException e ) { 132 throw new DatastoreException( e.getMessage(), e ); 133 } 134 } 135 } 136 137 /** 138 * Builds a SELECT statement to fetch features / properties that are stored in a related table. 139 * 140 * @param fetchContents 141 * table columns / functions to fetch 142 * @param relations 143 * table relations that lead to the table where the property is stored 144 * @param resultValues 145 * all retrieved columns from one result set row 146 * @param resultPosMap 147 * key class: SimpleContent, value class: Integer (this is the associated index in resultValues) 148 * @return the statement or null if the keys in resultValues contain NULL values 149 */ 150 private StatementBuffer buildSubsequentSelect( List<List<SimpleContent>> fetchContents, TableRelation[] relations, 151 Object[] resultValues, Map<SimpleContent, Integer> resultPosMap ) { 152 153 this.aliasGenerator.reset(); 154 String[] tableAliases = this.aliasGenerator.generateUniqueAliases( relations.length ); 155 156 StatementBuffer query = new StatementBuffer(); 157 query.append( "SELECT " ); 158 appendQualifiedContentList( query, tableAliases[tableAliases.length - 1], fetchContents ); 159 query.append( " FROM " ); 160 query.append( relations[0].getToTable() ); 161 query.append( " " ); 162 query.append( tableAliases[0] ); 163 164 // append joins 165 for ( int i = 1; i < relations.length; i++ ) { 166 query.append( " JOIN " ); 167 query.append( relations[i].getToTable() ); 168 query.append( " " ); 169 query.append( tableAliases[i] ); 170 query.append( " ON " ); 171 MappingField[] fromFields = relations[i].getFromFields(); 172 MappingField[] toFields = relations[i].getToFields(); 173 for ( int j = 0; j < fromFields.length; j++ ) { 174 query.append( tableAliases[i - 1] ); 175 query.append( '.' ); 176 query.append( fromFields[j].getField() ); 177 query.append( '=' ); 178 query.append( tableAliases[i] ); 179 query.append( '.' ); 180 query.append( toFields[j].getField() ); 181 } 182 } 183 184 // append key constraints 185 query.append( " WHERE " ); 186 MappingField[] fromFields = relations[0].getFromFields(); 187 MappingField[] toFields = relations[0].getToFields(); 188 for ( int i = 0; i < fromFields.length; i++ ) { 189 int resultPos = resultPosMap.get( fromFields[i] ); 190 Object keyValue = resultValues[resultPos]; 191 if ( keyValue == null ) { 192 return null; 193 } 194 query.append( tableAliases[0] ); 195 query.append( '.' ); 196 query.append( toFields[i].getField() ); 197 query.append( "=?" ); 198 query.addArgument( keyValue, toFields[i].getType() ); 199 if ( i != fromFields.length - 1 ) { 200 query.append( " AND " ); 201 } 202 } 203 return query; 204 } 205 206 /** 207 * Builds a SELECT statement to fetch the feature ids and the (concrete) feature types of feature properties that 208 * are stored in a related table (currently limited to *one* join table). 209 * <p> 210 * This is only necessary for feature properties that contain feature types with more than one possible 211 * substitution. 212 * 213 * @param relation1 214 * first table relation that leads to the join table 215 * @param relation2 216 * second table relation that leads to the table where the property is stored 217 * @param resultValues 218 * all retrieved columns from one result set row 219 * @param mappingFieldMap 220 * key class: MappingField, value class: Integer (this is the associated index in resultValues) 221 * @return the statement or null if the keys in resultValues contain NULL values 222 */ 223 private StatementBuffer buildFeatureTypeSelect( TableRelation relation1, TableRelation relation2, 224 Object[] resultValues, Map mappingFieldMap ) { 225 StatementBuffer query = new StatementBuffer(); 226 query.append( "SELECT " ); 227 // append feature type column 228 query.append( FT_COLUMN ); 229 // append feature id columns 230 MappingField[] fidFields = relation2.getFromFields(); 231 for ( int i = 0; i < fidFields.length; i++ ) { 232 query.append( ',' ); 233 query.append( fidFields[i].getField() ); 234 } 235 query.append( " FROM " ); 236 query.append( relation1.getToTable() ); 237 query.append( " WHERE " ); 238 // append key constraints 239 MappingField[] fromFields = relation1.getFromFields(); 240 MappingField[] toFields = relation1.getToFields(); 241 for ( int i = 0; i < fromFields.length; i++ ) { 242 Integer resultPos = (Integer) mappingFieldMap.get( fromFields[i] ); 243 Object keyValue = resultValues[resultPos.intValue()]; 244 if ( keyValue == null ) { 245 return null; 246 } 247 query.append( toFields[i].getField() ); 248 query.append( "=?" ); 249 query.addArgument( keyValue, toFields[i].getType() ); 250 if ( i != fromFields.length - 1 ) { 251 query.append( " AND " ); 252 } 253 } 254 return query; 255 } 256 257 /** 258 * Builds a SELECT statement to fetch the feature id and the (concrete) feature type of a feature property that is 259 * stored in a related table (with the fk in the current table). 260 * <p> 261 * This is only necessary for feature properties that contain feature types with more than one possible 262 * substitution. 263 * 264 * TODO: Select the FT_ column beforehand. 265 * 266 * @param relation 267 * table relation that leads to the subfeature table 268 * @param resultValues 269 * all retrieved columns from one result set row 270 * @param mappingFieldMap 271 * key class: MappingField, value class: Integer (this is the associated index in resultValues) 272 * @return the statement or null if the keys in resultValues contain NULL values 273 */ 274 private StatementBuffer buildFeatureTypeSelect( TableRelation relation, Object[] resultValues, Map mappingFieldMap ) { 275 StatementBuffer query = new StatementBuffer(); 276 query.append( "SELECT DISTINCT " ); 277 // append feature type column 278 query.append( FT_PREFIX + relation.getFromFields()[0].getField() ); 279 // append feature id columns 280 MappingField[] fidFields = relation.getFromFields(); 281 for ( int i = 0; i < fidFields.length; i++ ) { 282 query.append( ',' ); 283 query.append( fidFields[i].getField() ); 284 } 285 query.append( " FROM " ); 286 query.append( relation.getFromTable() ); 287 query.append( " WHERE " ); 288 // append key constraints 289 MappingField[] fromFields = relation.getFromFields(); 290 for ( int i = 0; i < fromFields.length; i++ ) { 291 Integer resultPos = (Integer) mappingFieldMap.get( fromFields[i] ); 292 Object keyValue = resultValues[resultPos.intValue()]; 293 if ( keyValue == null ) { 294 return null; 295 } 296 query.append( fromFields[i].getField() ); 297 query.append( "=?" ); 298 query.addArgument( keyValue, fromFields[i].getType() ); 299 if ( i != fromFields.length - 1 ) { 300 query.append( " AND " ); 301 } 302 } 303 return query; 304 } 305 306 /** 307 * Builds a SELECT statement that fetches one feature and it's properties. 308 * 309 * @param fid 310 * id of the feature to fetch 311 * @param table 312 * root table of the feature 313 * @param fetchContents 314 * @return the statement or null if the keys in resultValues contain NULL values 315 */ 316 private StatementBuffer buildFeatureSelect( FeatureId fid, String table, List<List<SimpleContent>> fetchContents ) { 317 318 StatementBuffer query = new StatementBuffer(); 319 query.append( "SELECT " ); 320 appendQualifiedContentList( query, table, fetchContents ); 321 query.append( " FROM " ); 322 query.append( table ); 323 query.append( " WHERE " ); 324 325 // append feature id constraints 326 MappingField[] fidFields = fid.getFidDefinition().getIdFields(); 327 for ( int i = 0; i < fidFields.length; i++ ) { 328 query.append( fidFields[i].getField() ); 329 query.append( "=?" ); 330 query.addArgument( fid.getValue( i ), fidFields[i].getType() ); 331 if ( i != fidFields.length - 1 ) { 332 query.append( " AND " ); 333 } 334 } 335 return query; 336 } 337 338 /** 339 * Extracts a feature from the values of a result set row. 340 * 341 * @param fid 342 * feature id of the feature 343 * @param requestedPropertyMap 344 * requested <code>MappedPropertyType</code>s mapped to <code>Collection</code> of 345 * <code>PropertyPath</code>s 346 * @param resultPosMap 347 * key class: MappingField, value class: Integer (this is the associated index in resultValues) 348 * @param resultValues 349 * all retrieved columns from one result set row 350 * @return the extracted feature 351 * @throws SQLException 352 * if a JDBC related error occurs 353 * @throws DatastoreException 354 * @throws UnknownCRSException 355 */ 356 protected Feature extractFeature( FeatureId fid, 357 Map<MappedPropertyType, Collection<PropertyPath>> requestedPropertyMap, 358 Map<SimpleContent, Integer> resultPosMap, Object[] resultValues ) 359 throws SQLException, DatastoreException, UnknownCRSException { 360 361 LOG.logDebug( "id = " + fid.getAsString() ); 362 363 this.featuresInGeneration.add( fid ); 364 365 // extract the requested properties of the feature 366 List<FeatureProperty> propertyList = new ArrayList<FeatureProperty>(); 367 for ( MappedPropertyType requestedProperty : requestedPropertyMap.keySet() ) { 368 Collection<PropertyPath> propertyPaths = requestedPropertyMap.get( requestedProperty ); 369 // PropertyPath[] requestingPaths = PropertyPathResolver.determineSubPropertyPaths 370 // (requestedProperty, propertyPaths); 371 Collection<FeatureProperty> props = extractProperties( requestedProperty, propertyPaths, resultPosMap, 372 resultValues ); 373 propertyList.addAll( props ); 374 } 375 FeatureProperty[] properties = propertyList.toArray( new FeatureProperty[propertyList.size()] ); 376 Feature feature = FeatureFactory.createFeature( fid.getAsString(), fid.getFeatureType(), properties ); 377 378 this.featureMap.put( fid, feature ); 379 return feature; 380 } 381 382 /** 383 * Extracts the feature id from the values of a result set row. 384 * 385 * @param ft 386 * feature type for which the id shall be extracted 387 * @param mfMap 388 * key class: MappingField, value class: Integer (this is the associated index in resultValues) 389 * @param resultValues 390 * all retrieved columns from one result set row 391 * @return the feature id 392 * @throws DatastoreException 393 */ 394 protected FeatureId extractFeatureId( MappedFeatureType ft, Map<SimpleContent, Integer> mfMap, Object[] resultValues ) 395 throws DatastoreException { 396 MappingField[] idFields = ft.getGMLId().getIdFields(); 397 Object[] idValues = new Object[idFields.length]; 398 for ( int i = 0; i < idFields.length; i++ ) { 399 Integer resultPos = mfMap.get( idFields[i] ); 400 Object idValue = resultValues[resultPos.intValue()]; 401 if ( idValue == null ) { 402 String msg = Messages.getMessage( "DATASTORE_FEATURE_ID_NULL", ft.getTable(), ft.getName(), 403 idFields[i].getField() ); 404 throw new DatastoreException( msg ); 405 } 406 idValues[i] = idValue; 407 } 408 return new FeatureId( ft, idValues ); 409 } 410 411 /** 412 * Extracts the properties of the given property type from the values of a result set row. If the property is stored 413 * in related table, only the key values are present in the result set row and more SELECTs are built and executed 414 * to build the property. 415 * <p> 416 * NOTE: If the property is not stored in a related table, only one FeatureProperty is returned, otherwise the 417 * number of properties depends on the multiplicity of the relation. 418 * 419 * @param pt 420 * the mapped property type to be extracted 421 * @param propertyPaths 422 * property paths that refer to the property to be extracted 423 * @param resultPosMap 424 * key class: SimpleContent, value class: Integer (this is the associated index in resultValues) 425 * @param resultValues 426 * all retrieved columns from one result set row 427 * @return Collection of FeatureProperty instances 428 * @throws SQLException 429 * if a JDBC related error occurs 430 * @throws DatastoreException 431 * @throws UnknownCRSException 432 */ 433 private Collection<FeatureProperty> extractProperties( MappedPropertyType pt, 434 Collection<PropertyPath> propertyPaths, 435 Map<SimpleContent, Integer> resultPosMap, 436 Object[] resultValues ) 437 throws SQLException, DatastoreException, UnknownCRSException { 438 439 Collection<FeatureProperty> result = null; 440 441 TableRelation[] tableRelations = pt.getTableRelations(); 442 if ( tableRelations != null && tableRelations.length != 0 ) { 443 LOG.logDebug( "Fetching related properties: '" + pt.getName() + "'..." ); 444 result = fetchRelatedProperties( pt.getName(), pt, propertyPaths, resultPosMap, resultValues ); 445 } else { 446 Object propertyValue = null; 447 if ( pt instanceof MappedSimplePropertyType ) { 448 SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent(); 449 if ( content instanceof MappingField ) { 450 Integer resultPos = resultPosMap.get( content ); 451 propertyValue = datastore.convertFromDBType( resultValues[resultPos.intValue()], 452 pt.getType() ); 453 } else if ( content instanceof ConstantContent ) { 454 propertyValue = ( (ConstantContent) content ).getValue(); 455 } else if ( content instanceof SQLFunctionCall ) { 456 Integer resultPos = resultPosMap.get( content ); 457 propertyValue = resultValues[resultPos.intValue()]; 458 } 459 } else if ( pt instanceof MappedGeometryPropertyType ) { 460 MappingGeometryField field = ( (MappedGeometryPropertyType) pt ).getMappingField(); 461 Integer resultPos = null; 462 SQLFunctionCall transformCall = this.fieldToTransformCall.get( field ); 463 if ( transformCall != null ) { 464 resultPos = resultPosMap.get( transformCall ); 465 } else { 466 resultPos = resultPosMap.get( field ); 467 } 468 propertyValue = resultValues[resultPos.intValue()]; 469 470 CoordinateSystem cs = ( (MappedGeometryPropertyType) pt ).getCS(); 471 if ( this.queryCS != null ) { 472 cs = this.queryCS; 473 } 474 propertyValue = this.datastore.convertDBToDeegreeGeometry( propertyValue, cs, conn ); 475 } else { 476 String msg = "Unsupported property type: '" + pt.getClass().getName() 477 + "' in QueryHandler.extractProperties(). "; 478 LOG.logError( msg ); 479 throw new IllegalArgumentException( msg ); 480 } 481 FeatureProperty property = FeatureFactory.createFeatureProperty( pt.getName(), propertyValue ); 482 result = new ArrayList<FeatureProperty>(); 483 result.add( property ); 484 } 485 return result; 486 } 487 488 /** 489 * Extracts a {@link FeatureId} from one result set row. 490 * 491 * @param ft 492 * @param rs 493 * @param startIndex 494 * @return feature id from result set row 495 * @throws SQLException 496 */ 497 private FeatureId extractFeatureId( MappedFeatureType ft, ResultSet rs, int startIndex ) 498 throws SQLException { 499 MappedGMLId gmlId = ft.getGMLId(); 500 MappingField[] idFields = gmlId.getIdFields(); 501 502 Object[] idValues = new Object[idFields.length]; 503 for ( int i = 0; i < idValues.length; i++ ) { 504 idValues[i] = rs.getObject( i + startIndex ); 505 } 506 return new FeatureId( ft, idValues ); 507 } 508 509 /** 510 * Determines the columns / functions that have to be fetched from the table of the given {@link MappedFeatureType} 511 * and associates identical columns / functions to avoid that the same column / function is SELECTed more than once. 512 * Identical columns are put into the same (inner) list. 513 * <p> 514 * The following {@link SimpleContent} instances of the {@link MappedFeatureType}s annotation are used to build the 515 * list: 516 * <ul> 517 * <li>MappingFields from the wfs:gmlId - annotation element of the feature type definition</li> 518 * <li>MappingFields in the annotations of the property element definitions; if the property's content is stored in 519 * a related table, the MappingFields used in the first wfs:Relation element's wfs:From element are considered</li> 520 * <li>SQLFunctionCalls in the annotations of the property element definitions</li> 521 * </ul> 522 * 523 * @param ft 524 * feature type for which the content list is built 525 * @param requestedProps 526 * requested properties 527 * @return List of Lists (that contains SimpleContent instance that refer the same column) 528 * @throws DatastoreException 529 */ 530 protected List<List<SimpleContent>> determineFetchContents( MappedFeatureType ft, PropertyType[] requestedProps ) 531 throws DatastoreException { 532 533 List<List<SimpleContent>> fetchList = new ArrayList<List<SimpleContent>>(); 534 535 // helper lookup map (column names -> referencing MappingField instances) 536 Map<String, List<SimpleContent>> columnsMap = new HashMap<String, List<SimpleContent>>(); 537 538 // add table columns that are necessary to build the feature's gml id 539 MappingField[] idFields = ft.getGMLId().getIdFields(); 540 for ( int i = 0; i < idFields.length; i++ ) { 541 List<SimpleContent> mappingFieldList = columnsMap.get( idFields[i].getField() ); 542 if ( mappingFieldList == null ) { 543 mappingFieldList = new ArrayList<SimpleContent>(); 544 } 545 mappingFieldList.add( idFields[i] ); 546 columnsMap.put( idFields[i].getField(), mappingFieldList ); 547 } 548 549 // add columns that are necessary to build the requested feature properties 550 for ( int i = 0; i < requestedProps.length; i++ ) { 551 MappedPropertyType pt = (MappedPropertyType) requestedProps[i]; 552 TableRelation[] tableRelations = pt.getTableRelations(); 553 if ( tableRelations != null && tableRelations.length != 0 ) { 554 // if property is not stored in feature type's table, retrieve key fields of 555 // the first relation's 'From' element 556 MappingField[] fields = tableRelations[0].getFromFields(); 557 for ( int k = 0; k < fields.length; k++ ) { 558 List<SimpleContent> list = columnsMap.get( fields[k].getField() ); 559 if ( list == null ) { 560 list = new ArrayList<SimpleContent>(); 561 } 562 list.add( fields[k] ); 563 columnsMap.put( fields[k].getField(), list ); 564 } 565 // if (content instanceof FeaturePropertyContent) { 566 // if (tableRelations.length == 1) { 567 // // if feature property contains an abstract feature type, retrieve 568 // // feature type as well (stored in column named "FT_fk") 569 // MappedFeatureType subFeatureType = ( (FeaturePropertyContent) content ) 570 // .getFeatureTypeReference().getFeatureType(); 571 // if (subFeatureType.isAbstract()) { 572 // String typeColumn = FT_PREFIX + fields [0].getField(); 573 // columnsMap.put (typeColumn, new ArrayList ()); 574 // } 575 // } 576 // } 577 } else { 578 String column = null; 579 SimpleContent content = null; 580 if ( pt instanceof MappedSimplePropertyType ) { 581 content = ( (MappedSimplePropertyType) pt ).getContent(); 582 if ( content instanceof MappingField ) { 583 column = ( (MappingField) content ).getField(); 584 } else { 585 // ignore virtual properties here (handled below) 586 continue; 587 } 588 } else if ( pt instanceof MappedGeometryPropertyType ) { 589 content = determineFetchContent( (MappedGeometryPropertyType) pt ); 590 column = ( (MappedGeometryPropertyType) pt ).getMappingField().getField(); 591 } else { 592 assert false; 593 } 594 List<SimpleContent> contentList = columnsMap.get( column ); 595 if ( contentList == null ) { 596 contentList = new ArrayList<SimpleContent>(); 597 } 598 contentList.add( content ); 599 columnsMap.put( column, contentList ); 600 } 601 } 602 603 fetchList.addAll( columnsMap.values() ); 604 605 // add functions that are necessary to build the requested feature properties 606 for ( int i = 0; i < requestedProps.length; i++ ) { 607 MappedPropertyType pt = (MappedPropertyType) requestedProps[i]; 608 if ( pt instanceof MappedSimplePropertyType ) { 609 SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent(); 610 if ( content instanceof SQLFunctionCall ) { 611 List<SimpleContent> functionCallList = new ArrayList<SimpleContent>( 1 ); 612 functionCallList.add( content ); 613 fetchList.add( functionCallList ); 614 } else { 615 // ignore other content types here 616 continue; 617 } 618 } 619 620 } 621 return fetchList; 622 } 623 624 /** 625 * Determines a {@link SimpleContent} object that represents the queried {@link MappedGeometryProperty} in the 626 * requested SRS. 627 * <p> 628 * <ul> 629 * <li>If the query SRS is identical to the geometry field's SRS (and thus the SRS of the stored geometry, the 630 * corresponding {@link MappingGeometryField} is returned.</li> 631 * <li>If the query SRS differs from the geometry field's SRS (and thus the SRS of the stored geometry, an 632 * {@link SQLFunctionCall} is returned that refers to the stored geometry, but transforms it to the queried SRS.</li> 633 * </ul> 634 * 635 * @param pt 636 * geometry property 637 * @return a <code>SimpleContent</code> instance that represents the queried geometry property 638 * @throws DatastoreException 639 * if the transform call cannot be generated 640 */ 641 private SimpleContent determineFetchContent( MappedGeometryPropertyType pt ) 642 throws DatastoreException { 643 644 MappingGeometryField field = pt.getMappingField(); 645 SimpleContent content = field; 646 647 String queriedSRS = this.datastore.checkTransformation( pt, this.query.getSrsName() ); 648 if ( queriedSRS != null ) { 649 content = this.fieldToTransformCall.get( field ); 650 if ( content == null ) { 651 content = this.datastore.buildSRSTransformCall( pt, queriedSRS ); 652 this.fieldToTransformCall.put( field, (SQLFunctionCall) content ); 653 } 654 } 655 return content; 656 } 657 658 /** 659 * Retrieves the feature with the given feature id. 660 * 661 * @param fid 662 * @param requestedPaths 663 * @return the feature with the given type and feature id, may be null 664 * @throws SQLException 665 * @throws DatastoreException 666 * @throws UnknownCRSException 667 */ 668 private Feature fetchFeature( FeatureId fid, PropertyPath[] requestedPaths ) 669 throws SQLException, DatastoreException, UnknownCRSException { 670 671 Feature feature = null; 672 MappedFeatureType ft = fid.getFeatureType(); 673 // TODO what about aliases here? 674 Map<MappedPropertyType, Collection<PropertyPath>> requestedPropMap = PropertyPathResolver.determineFetchProperties( 675 ft, 676 null, 677 requestedPaths ); 678 MappedPropertyType[] requestedProps = requestedPropMap.keySet().toArray( 679 new MappedPropertyType[requestedPropMap.size()] ); 680 681 if ( requestedProps.length > 0 ) { 682 683 // determine contents (fields / functions) that must be SELECTed from root table 684 List<List<SimpleContent>> fetchContents = determineFetchContents( ft, requestedProps ); 685 Map<SimpleContent, Integer> resultPosMap = buildResultPosMap( fetchContents ); 686 687 // build feature query 688 StatementBuffer query = buildFeatureSelect( fid, ft.getTable(), fetchContents ); 689 LOG.logDebug( "Feature query: '" + query + "'" ); 690 Object[] resultValues = new Object[fetchContents.size()]; 691 PreparedStatement stmt = null; 692 ResultSet rs = null; 693 try { 694 stmt = this.datastore.prepareStatement( this.conn, query ); 695 rs = stmt.executeQuery(); 696 697 if ( rs.next() ) { 698 // collect result values 699 for ( int i = 0; i < resultValues.length; i++ ) { 700 resultValues[i] = rs.getObject( i + 1 ); 701 } 702 feature = extractFeature( fid, requestedPropMap, resultPosMap, resultValues ); 703 } else { 704 String msg = Messages.getMessage( "DATASTORE_FEATURE_QUERY_NO_RESULT", query.getQueryString() ); 705 LOG.logError( msg ); 706 throw new DatastoreException( msg ); 707 } 708 if ( rs.next() ) { 709 String msg = Messages.getMessage( "DATASTORE_FEATURE_QUERY_MORE_THAN_ONE_RESULT", 710 query.getQueryString() ); 711 LOG.logError( msg ); 712 throw new DatastoreException( msg ); 713 } 714 } finally { 715 try { 716 if ( rs != null ) { 717 rs.close(); 718 } 719 } finally { 720 if ( stmt != null ) { 721 stmt.close(); 722 } 723 } 724 } 725 } 726 return feature; 727 } 728 729 /** 730 * 731 * @param propertyName 732 * @param pt 733 * @param propertyPaths 734 * property paths that refer to the property to be extracted 735 * @param resultPosMap 736 * key class: MappingField, value class: Integer (this is the associated index in resultValues) 737 * @param resultValues 738 * all retrieved columns from one result set row 739 * @return Collection of FeatureProperty instances 740 * @throws SQLException 741 * if a JDBC related error occurs 742 * @throws DatastoreException 743 * @throws UnknownCRSException 744 */ 745 private Collection<FeatureProperty> fetchRelatedProperties( QualifiedName propertyName, MappedPropertyType pt, 746 Collection<PropertyPath> propertyPaths, 747 Map<SimpleContent, Integer> resultPosMap, 748 Object[] resultValues ) 749 throws SQLException, DatastoreException, UnknownCRSException { 750 751 Collection<FeatureProperty> result = new ArrayList<FeatureProperty>( 100 ); 752 PreparedStatement stmt = null; 753 ResultSet rs = null; 754 try { 755 if ( pt instanceof MappedSimplePropertyType ) { 756 SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent(); 757 758 // TODO check for invalid content types 759 List<SimpleContent> fetchContents = new ArrayList<SimpleContent>( 1 ); 760 List<List<SimpleContent>> fetchContentsList = new ArrayList<List<SimpleContent>>( 1 ); 761 fetchContents.add( content ); 762 fetchContentsList.add( fetchContents ); 763 764 StatementBuffer query = buildSubsequentSelect( fetchContentsList, pt.getTableRelations(), resultValues, 765 resultPosMap ); 766 LOG.logDebug( "Subsequent query: '" + query + "'" ); 767 if ( query != null ) { 768 stmt = this.datastore.prepareStatement( this.conn, query ); 769 rs = stmt.executeQuery(); 770 while ( rs.next() ) { 771 Object propertyValue = datastore.convertFromDBType( rs.getObject( 1 ), 772 pt.getType() ); 773 FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, propertyValue ); 774 result.add( property ); 775 } 776 } 777 778 } else if ( pt instanceof MappedGeometryPropertyType ) { 779 SimpleContent content = ( (MappedGeometryPropertyType) pt ).getMappingField(); 780 CoordinateSystem cs = ( (MappedGeometryPropertyType) pt ).getCS(); 781 782 if ( this.queryCS != null ) { 783 cs = this.queryCS; 784 } 785 content = determineFetchContent( (MappedGeometryPropertyType) pt ); 786 787 List<SimpleContent> fetchContents = new ArrayList<SimpleContent>( 1 ); 788 List<List<SimpleContent>> fetchContentsList = new ArrayList<List<SimpleContent>>( 1 ); 789 fetchContents.add( content ); 790 fetchContentsList.add( fetchContents ); 791 792 StatementBuffer query = buildSubsequentSelect( fetchContentsList, pt.getTableRelations(), resultValues, 793 resultPosMap ); 794 LOG.logDebug( "Subsequent query: '" + query + "'" ); 795 if ( query != null ) { 796 stmt = this.datastore.prepareStatement( this.conn, query ); 797 rs = stmt.executeQuery(); 798 while ( rs.next() ) { 799 Object value = rs.getObject( 1 ); 800 Geometry geometry = this.datastore.convertDBToDeegreeGeometry( value, cs, this.conn ); 801 FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, geometry ); 802 result.add( property ); 803 } 804 } 805 } else if ( pt instanceof MappedFeaturePropertyType ) { 806 MappedFeatureType ft = ( (MappedFeaturePropertyType) pt ).getFeatureTypeReference().getFeatureType(); 807 MappedFeatureType[] substitutions = ft.getConcreteSubstitutions(); 808 if ( substitutions.length > 1 ) { 809 // if feature type has more than one concrete substitution, determine concrete 810 // feature type first 811 String msg = StringTools.concat( 200, "FeatureType '", ft.getName(), 812 "' has more than one concrete ", 813 "substitution. Need to determine ", "feature type table first." ); 814 LOG.logDebug( msg ); 815 LOG.logDebug( "Property: " + pt.getName() ); 816 TableRelation[] tableRelations = pt.getTableRelations(); 817 if ( tableRelations.length == 2 ) { 818 StatementBuffer query = buildFeatureTypeSelect( tableRelations[0], tableRelations[1], 819 resultValues, resultPosMap ); 820 LOG.logDebug( "Feature type (and id) query: '" + query + "'" ); 821 if ( query != null ) { 822 stmt = this.datastore.prepareStatement( this.conn, query ); 823 rs = stmt.executeQuery(); 824 while ( rs.next() ) { 825 String featureTypeName = rs.getString( 1 ); 826 MappedFeatureType concreteFeatureType = ft.getGMLSchema().getFeatureType( 827 featureTypeName ); 828 if ( concreteFeatureType == null ) { 829 msg = StringTools.concat( 200, "Lookup of concrete feature type '", 830 featureTypeName, "' failed: ", 831 " Inconsistent featuretype column!?" ); 832 LOG.logError( msg ); 833 throw new DatastoreException( msg ); 834 } 835 FeatureId fid = extractFeatureId( concreteFeatureType, rs, 2 ); 836 msg = StringTools.concat( 200, "Subfeature '", fid.getAsString(), 837 "' has concrete feature type '", 838 concreteFeatureType.getName(), "'." ); 839 LOG.logDebug( msg ); 840 841 if ( !this.featuresInGeneration.contains( fid ) ) { 842 PropertyPath[] subPropertyPaths = PropertyPathResolver.determineSubPropertyPaths( 843 concreteFeatureType, 844 propertyPaths ); 845 Feature feature = fetchFeature( fid, subPropertyPaths ); 846 if ( feature != null ) { 847 FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, 848 feature ); 849 result.add( property ); 850 } 851 } else { 852 FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, null ); 853 addToFidToPropertyMap( fid, property ); 854 result.add( property ); 855 } 856 } 857 } 858 } else if ( tableRelations.length == 1 ) { 859 StatementBuffer query = buildFeatureTypeSelect( tableRelations[0], resultValues, resultPosMap ); 860 LOG.logDebug( "Feature type (and id) query: '" + query + "'" ); 861 if ( query != null ) { 862 stmt = this.datastore.prepareStatement( this.conn, query ); 863 rs = stmt.executeQuery(); 864 while ( rs.next() ) { 865 String featureTypeName = rs.getString( 1 ); 866 MappedFeatureType concreteFeatureType = ft.getGMLSchema().getFeatureType( 867 featureTypeName ); 868 if ( concreteFeatureType == null ) { 869 msg = StringTools.concat( 200, "Lookup of concrete feature type '", 870 featureTypeName, "' failed: ", 871 " Inconsistent featuretype column!?" ); 872 LOG.logError( msg ); 873 throw new DatastoreException( msg ); 874 } 875 876 FeatureId fid = extractFeatureId( concreteFeatureType, rs, 2 ); 877 878 msg = StringTools.concat( 200, "Subfeature '", fid.getAsString(), 879 "' has concrete feature type '", 880 concreteFeatureType.getName(), "'." ); 881 LOG.logDebug( msg ); 882 883 FeatureProperty property = null; 884 if ( !this.featuresInGeneration.contains( fid ) ) { 885 PropertyPath[] subPropertyPaths = PropertyPathResolver.determineSubPropertyPaths( 886 concreteFeatureType, 887 propertyPaths ); 888 Feature feature = fetchFeature( fid, subPropertyPaths ); 889 if ( feature != null ) { 890 property = FeatureFactory.createFeatureProperty( propertyName, feature ); 891 result.add( property ); 892 } 893 894 } else { 895 property = FeatureFactory.createFeatureProperty( propertyName, null ); 896 addToFidToPropertyMap( fid, property ); 897 result.add( property ); 898 } 899 } 900 } 901 } else { 902 msg = StringTools.concat( 200, "Querying of feature properties ", 903 "with a content type with more than one ", 904 "concrete substitution is not implemented for ", 905 tableRelations.length, " TableRelations." ); 906 throw new DatastoreException( msg ); 907 } 908 } else { 909 // feature type is the only substitutable concrete feature type 910 PropertyPath[] subPropertyPaths = PropertyPathResolver.determineSubPropertyPaths( ft, propertyPaths ); 911 // TODO aliases? 912 Map<MappedPropertyType, Collection<PropertyPath>> requestedPropertiesMap = PropertyPathResolver.determineFetchProperties( 913 ft, 914 null, 915 subPropertyPaths ); 916 MappedPropertyType[] requestedProperties = requestedPropertiesMap.keySet().toArray( 917 new MappedPropertyType[requestedPropertiesMap.size()] ); 918 919 // determine contents (fields / functions) that needs to be SELECTed from 920 // current table 921 List<List<SimpleContent>> fetchContents = determineFetchContents( ft, requestedProperties ); 922 Map<SimpleContent, Integer> newResultPosMap = buildResultPosMap( fetchContents ); 923 924 StatementBuffer query = buildSubsequentSelect( fetchContents, pt.getTableRelations(), resultValues, 925 resultPosMap ); 926 LOG.logDebug( "Subsequent query: '" + query + "'" ); 927 928 if ( query != null ) { 929 Object[] newResultValues = new Object[fetchContents.size()]; 930 stmt = this.datastore.prepareStatement( this.conn, query ); 931 rs = stmt.executeQuery(); 932 while ( rs.next() ) { 933 // cache result values 934 for ( int i = 0; i < newResultValues.length; i++ ) { 935 newResultValues[i] = rs.getObject( i + 1 ); 936 } 937 FeatureId fid = extractFeatureId( ft, newResultPosMap, newResultValues ); 938 FeatureProperty property = null; 939 if ( !this.featuresInGeneration.contains( fid ) ) { 940 Feature feature = extractFeature( fid, requestedPropertiesMap, newResultPosMap, 941 newResultValues ); 942 property = FeatureFactory.createFeatureProperty( propertyName, feature ); 943 } else { 944 property = FeatureFactory.createFeatureProperty( propertyName, null ); 945 addToFidToPropertyMap( fid, property ); 946 } 947 result.add( property ); 948 } 949 } 950 } 951 } else { 952 String msg = "Unsupported content type: '" + pt.getClass().getName() 953 + "' in QueryHandler.fetchRelatedProperties()."; 954 throw new IllegalArgumentException( msg ); 955 } 956 } finally { 957 try { 958 if ( rs != null ) { 959 rs.close(); 960 } 961 if ( stmt != null ) { 962 stmt.close(); 963 } 964 } finally { 965 if ( stmt != null ) { 966 stmt.close(); 967 } 968 } 969 } 970 return result; 971 } 972 973 private void addToFidToPropertyMap( FeatureId fid, FeatureProperty property ) { 974 List<FeatureProperty> properties = this.fidToPropertyMap.get( fid ); 975 if ( properties == null ) { 976 properties = new ArrayList<FeatureProperty>(); 977 this.fidToPropertyMap.put( fid, properties ); 978 } 979 properties.add( property ); 980 } 981 982 protected void appendQualifiedContentList( StatementBuffer query, String tableAlias, 983 List<List<SimpleContent>> fetchContents ) { 984 985 for ( int i = 0; i < fetchContents.size(); i++ ) { 986 SimpleContent content = fetchContents.get( i ).get( 0 ); 987 if ( content instanceof MappingField ) { 988 appendQualifiedColumn( query, tableAlias, ( (MappingField) content ).getField() ); 989 } else if ( content instanceof SQLFunctionCall ) { 990 this.vcProvider.appendSQLFunctionCall( query, tableAlias, (SQLFunctionCall) content ); 991 } else { 992 assert false; 993 } 994 if ( i != fetchContents.size() - 1 ) { 995 query.append( "," ); 996 } 997 } 998 } 999 1000 /** 1001 * Builds a lookup map that allows to find the index (position in the {@link ResultSet}) by the 1002 * {@link SimpleContent} instance that makes it necessary to fetch it. 1003 * 1004 * @param fetchContents 1005 * @return key: SimpleContent, value: Integer (position in ResultSet) 1006 */ 1007 protected Map<SimpleContent, Integer> buildResultPosMap( List<List<SimpleContent>> fetchContents ) { 1008 1009 Map<SimpleContent, Integer> resultPosMap = new HashMap<SimpleContent, Integer>(); 1010 for ( int i = 0; i < fetchContents.size(); i++ ) { 1011 for ( SimpleContent content : fetchContents.get( i ) ) { 1012 resultPosMap.put( content, i ); 1013 } 1014 } 1015 return resultPosMap; 1016 } 1017 }