001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/io/datastore/sql/FeatureFetcher.java $ 002 /*---------------- FILE HEADER ------------------------------------------ 003 004 This file is part of deegree. 005 Copyright (C) 2001-2008 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: apoth $ 098 * 099 * @version $Revision: 9342 $, $Date: 2007-12-27 13:32:57 +0100 (Do, 27 Dez 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()], pt.getType() ); 452 } else if ( content instanceof ConstantContent ) { 453 propertyValue = ( (ConstantContent) content ).getValue(); 454 } else if ( content instanceof SQLFunctionCall ) { 455 Integer resultPos = resultPosMap.get( content ); 456 propertyValue = resultValues[resultPos.intValue()]; 457 } 458 } else if ( pt instanceof MappedGeometryPropertyType ) { 459 MappingGeometryField field = ( (MappedGeometryPropertyType) pt ).getMappingField(); 460 Integer resultPos = null; 461 SQLFunctionCall transformCall = this.fieldToTransformCall.get( field ); 462 if ( transformCall != null ) { 463 resultPos = resultPosMap.get( transformCall ); 464 } else { 465 resultPos = resultPosMap.get( field ); 466 } 467 propertyValue = resultValues[resultPos.intValue()]; 468 469 CoordinateSystem cs = ( (MappedGeometryPropertyType) pt ).getCS(); 470 if ( this.queryCS != null ) { 471 cs = this.queryCS; 472 } 473 propertyValue = this.datastore.convertDBToDeegreeGeometry( propertyValue, cs, conn ); 474 } else { 475 String msg = "Unsupported property type: '" + pt.getClass().getName() 476 + "' in QueryHandler.extractProperties(). "; 477 LOG.logError( msg ); 478 throw new IllegalArgumentException( msg ); 479 } 480 FeatureProperty property = FeatureFactory.createFeatureProperty( pt.getName(), propertyValue ); 481 result = new ArrayList<FeatureProperty>(); 482 result.add( property ); 483 } 484 return result; 485 } 486 487 /** 488 * Extracts a {@link FeatureId} from one result set row. 489 * 490 * @param ft 491 * @param rs 492 * @param startIndex 493 * @return feature id from result set row 494 * @throws SQLException 495 */ 496 private FeatureId extractFeatureId( MappedFeatureType ft, ResultSet rs, int startIndex ) 497 throws SQLException { 498 MappedGMLId gmlId = ft.getGMLId(); 499 MappingField[] idFields = gmlId.getIdFields(); 500 501 Object[] idValues = new Object[idFields.length]; 502 for ( int i = 0; i < idValues.length; i++ ) { 503 idValues[i] = rs.getObject( i + startIndex ); 504 } 505 return new FeatureId( ft, idValues ); 506 } 507 508 /** 509 * Determines the columns / functions that have to be fetched from the table of the given {@link MappedFeatureType} 510 * and associates identical columns / functions to avoid that the same column / function is SELECTed more than once. 511 * <p> 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; if the property's (derived) content 521 * is stored in a related table, the MappingFields used in the first wfs:Relation element's wfs:From element are 522 * considered</li> 523 * </ul> 524 * 525 * @param ft 526 * feature type for which the content list is built 527 * @param requestedProps 528 * requested properties 529 * @return List of Lists (that contains SimpleContent instance that refer the same column) 530 * @throws DatastoreException 531 */ 532 protected List<List<SimpleContent>> determineFetchContents( MappedFeatureType ft, PropertyType[] requestedProps ) 533 throws DatastoreException { 534 535 List<List<SimpleContent>> fetchList = new ArrayList<List<SimpleContent>>(); 536 537 // helper lookup map (column names -> referencing MappingField instances) 538 Map<String, List<SimpleContent>> columnsMap = new HashMap<String, List<SimpleContent>>(); 539 540 // add table columns that are necessary to build the feature's gml id 541 MappingField[] idFields = ft.getGMLId().getIdFields(); 542 for ( int i = 0; i < idFields.length; i++ ) { 543 List<SimpleContent> mappingFieldList = columnsMap.get( idFields[i].getField() ); 544 if ( mappingFieldList == null ) { 545 mappingFieldList = new ArrayList<SimpleContent>(); 546 } 547 mappingFieldList.add( idFields[i] ); 548 columnsMap.put( idFields[i].getField(), mappingFieldList ); 549 } 550 551 // add columns that are necessary to build the requested feature properties 552 for ( int i = 0; i < requestedProps.length; i++ ) { 553 MappedPropertyType pt = (MappedPropertyType) requestedProps[i]; 554 TableRelation[] tableRelations = pt.getTableRelations(); 555 if ( tableRelations != null && tableRelations.length != 0 ) { 556 // if property is not stored in feature type's table, retrieve key fields of 557 // the first relation's 'From' element 558 MappingField[] fields = tableRelations[0].getFromFields(); 559 for ( int k = 0; k < fields.length; k++ ) { 560 List<SimpleContent> list = columnsMap.get( fields[k].getField() ); 561 if ( list == null ) { 562 list = new ArrayList<SimpleContent>(); 563 } 564 list.add( fields[k] ); 565 columnsMap.put( fields[k].getField(), list ); 566 } 567 // if (content instanceof FeaturePropertyContent) { 568 // if (tableRelations.length == 1) { 569 // // if feature property contains an abstract feature type, retrieve 570 // // feature type as well (stored in column named "FT_fk") 571 // MappedFeatureType subFeatureType = ( (FeaturePropertyContent) content ) 572 // .getFeatureTypeReference().getFeatureType(); 573 // if (subFeatureType.isAbstract()) { 574 // String typeColumn = FT_PREFIX + fields [0].getField(); 575 // columnsMap.put (typeColumn, new ArrayList ()); 576 // } 577 // } 578 // } 579 } else { 580 String column = null; 581 SimpleContent content = null; 582 if ( pt instanceof MappedSimplePropertyType ) { 583 content = ( (MappedSimplePropertyType) pt ).getContent(); 584 if ( content instanceof MappingField ) { 585 column = ( (MappingField) content ).getField(); 586 } else { 587 // ignore virtual properties here (handled below) 588 continue; 589 } 590 } else if ( pt instanceof MappedGeometryPropertyType ) { 591 content = determineFetchContent( (MappedGeometryPropertyType) pt ); 592 column = ( (MappedGeometryPropertyType) pt ).getMappingField().getField(); 593 } else { 594 assert false; 595 } 596 List<SimpleContent> contentList = columnsMap.get( column ); 597 if ( contentList == null ) { 598 contentList = new ArrayList<SimpleContent>(); 599 } 600 contentList.add( content ); 601 columnsMap.put( column, contentList ); 602 } 603 } 604 605 fetchList.addAll( columnsMap.values() ); 606 607 // add functions that are necessary to build the requested feature properties 608 for ( int i = 0; i < requestedProps.length; i++ ) { 609 MappedPropertyType pt = (MappedPropertyType) requestedProps[i]; 610 TableRelation[] tableRelations = pt.getTableRelations(); 611 if ( tableRelations == null || tableRelations.length == 0 ) { 612 if ( pt instanceof MappedSimplePropertyType ) { 613 SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent(); 614 if ( content instanceof SQLFunctionCall ) { 615 List<SimpleContent> functionCallList = new ArrayList<SimpleContent>( 1 ); 616 functionCallList.add( content ); 617 fetchList.add( functionCallList ); 618 } else { 619 // ignore other content types here 620 continue; 621 } 622 } 623 } 624 } 625 return fetchList; 626 } 627 628 /** 629 * Determines a {@link SimpleContent} object that represents the queried {@link MappedGeometryProperty} in the 630 * requested SRS. 631 * <p> 632 * <ul> 633 * <li>If the query SRS is identical to the geometry field's SRS (and thus the SRS of the stored geometry, the 634 * corresponding {@link MappingGeometryField} is returned.</li> 635 * <li>If the query SRS differs from the geometry field's SRS (and thus the SRS of the stored geometry, an 636 * {@link SQLFunctionCall} is returned that refers to the stored geometry, but transforms it to the queried SRS.</li> 637 * </ul> 638 * 639 * @param pt 640 * geometry property 641 * @return a <code>SimpleContent</code> instance that represents the queried geometry property 642 * @throws DatastoreException 643 * if the transform call cannot be generated 644 */ 645 private SimpleContent determineFetchContent( MappedGeometryPropertyType pt ) 646 throws DatastoreException { 647 648 MappingGeometryField field = pt.getMappingField(); 649 SimpleContent content = field; 650 651 String queriedSRS = this.datastore.checkTransformation( pt, this.query.getSrsName() ); 652 if ( queriedSRS != null ) { 653 content = this.fieldToTransformCall.get( field ); 654 if ( content == null ) { 655 content = this.datastore.buildSRSTransformCall( pt, queriedSRS ); 656 this.fieldToTransformCall.put( field, (SQLFunctionCall) content ); 657 } 658 } 659 return content; 660 } 661 662 /** 663 * Retrieves the feature with the given feature id. 664 * 665 * @param fid 666 * @param requestedPaths 667 * @return the feature with the given type and feature id, may be null 668 * @throws SQLException 669 * @throws DatastoreException 670 * @throws UnknownCRSException 671 */ 672 private Feature fetchFeature( FeatureId fid, PropertyPath[] requestedPaths ) 673 throws SQLException, DatastoreException, UnknownCRSException { 674 675 Feature feature = null; 676 MappedFeatureType ft = fid.getFeatureType(); 677 // TODO what about aliases here? 678 Map<MappedPropertyType, Collection<PropertyPath>> requestedPropMap = PropertyPathResolver.determineFetchProperties( 679 ft, 680 null, 681 requestedPaths ); 682 MappedPropertyType[] requestedProps = requestedPropMap.keySet().toArray( 683 new MappedPropertyType[requestedPropMap.size()] ); 684 685 if ( requestedProps.length > 0 ) { 686 687 // determine contents (fields / functions) that must be SELECTed from root table 688 List<List<SimpleContent>> fetchContents = determineFetchContents( ft, requestedProps ); 689 Map<SimpleContent, Integer> resultPosMap = buildResultPosMap( fetchContents ); 690 691 // build feature query 692 StatementBuffer query = buildFeatureSelect( fid, ft.getTable(), fetchContents ); 693 LOG.logDebug( "Feature query: '" + query + "'" ); 694 Object[] resultValues = new Object[fetchContents.size()]; 695 PreparedStatement stmt = null; 696 ResultSet rs = null; 697 try { 698 stmt = this.datastore.prepareStatement( this.conn, query ); 699 rs = stmt.executeQuery(); 700 701 if ( rs.next() ) { 702 // collect result values 703 for ( int i = 0; i < resultValues.length; i++ ) { 704 resultValues[i] = rs.getObject( i + 1 ); 705 } 706 feature = extractFeature( fid, requestedPropMap, resultPosMap, resultValues ); 707 } else { 708 String msg = Messages.getMessage( "DATASTORE_FEATURE_QUERY_NO_RESULT", query.getQueryString() ); 709 LOG.logError( msg ); 710 throw new DatastoreException( msg ); 711 } 712 if ( rs.next() ) { 713 String msg = Messages.getMessage( "DATASTORE_FEATURE_QUERY_MORE_THAN_ONE_RESULT", 714 query.getQueryString() ); 715 LOG.logError( msg ); 716 throw new DatastoreException( msg ); 717 } 718 } finally { 719 try { 720 if ( rs != null ) { 721 rs.close(); 722 } 723 } finally { 724 if ( stmt != null ) { 725 stmt.close(); 726 } 727 } 728 } 729 } 730 return feature; 731 } 732 733 /** 734 * 735 * @param propertyName 736 * @param pt 737 * @param propertyPaths 738 * property paths that refer to the property to be extracted 739 * @param resultPosMap 740 * key class: MappingField, value class: Integer (this is the associated index in resultValues) 741 * @param resultValues 742 * all retrieved columns from one result set row 743 * @return Collection of FeatureProperty instances 744 * @throws SQLException 745 * if a JDBC related error occurs 746 * @throws DatastoreException 747 * @throws UnknownCRSException 748 */ 749 private Collection<FeatureProperty> fetchRelatedProperties( QualifiedName propertyName, MappedPropertyType pt, 750 Collection<PropertyPath> propertyPaths, 751 Map<SimpleContent, Integer> resultPosMap, 752 Object[] resultValues ) 753 throws SQLException, DatastoreException, UnknownCRSException { 754 755 Collection<FeatureProperty> result = new ArrayList<FeatureProperty>( 100 ); 756 PreparedStatement stmt = null; 757 ResultSet rs = null; 758 try { 759 if ( pt instanceof MappedSimplePropertyType ) { 760 SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent(); 761 762 // TODO check for invalid content types 763 List<SimpleContent> fetchContents = new ArrayList<SimpleContent>( 1 ); 764 List<List<SimpleContent>> fetchContentsList = new ArrayList<List<SimpleContent>>( 1 ); 765 fetchContents.add( content ); 766 fetchContentsList.add( fetchContents ); 767 768 StatementBuffer query = buildSubsequentSelect( fetchContentsList, pt.getTableRelations(), resultValues, 769 resultPosMap ); 770 LOG.logDebug( "Subsequent query: '" + query + "'" ); 771 if ( query != null ) { 772 stmt = this.datastore.prepareStatement( this.conn, query ); 773 rs = stmt.executeQuery(); 774 while ( rs.next() ) { 775 Object propertyValue = datastore.convertFromDBType( rs.getObject( 1 ), pt.getType() ); 776 FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, propertyValue ); 777 result.add( property ); 778 } 779 } 780 781 } else if ( pt instanceof MappedGeometryPropertyType ) { 782 SimpleContent content = ( (MappedGeometryPropertyType) pt ).getMappingField(); 783 CoordinateSystem cs = ( (MappedGeometryPropertyType) pt ).getCS(); 784 785 if ( this.queryCS != null ) { 786 cs = this.queryCS; 787 } 788 content = determineFetchContent( (MappedGeometryPropertyType) pt ); 789 790 List<SimpleContent> fetchContents = new ArrayList<SimpleContent>( 1 ); 791 List<List<SimpleContent>> fetchContentsList = new ArrayList<List<SimpleContent>>( 1 ); 792 fetchContents.add( content ); 793 fetchContentsList.add( fetchContents ); 794 795 StatementBuffer query = buildSubsequentSelect( fetchContentsList, pt.getTableRelations(), resultValues, 796 resultPosMap ); 797 LOG.logDebug( "Subsequent query: '" + query + "'" ); 798 if ( query != null ) { 799 stmt = this.datastore.prepareStatement( this.conn, query ); 800 rs = stmt.executeQuery(); 801 while ( rs.next() ) { 802 Object value = rs.getObject( 1 ); 803 Geometry geometry = this.datastore.convertDBToDeegreeGeometry( value, cs, this.conn ); 804 FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, geometry ); 805 result.add( property ); 806 } 807 } 808 } else if ( pt instanceof MappedFeaturePropertyType ) { 809 MappedFeatureType ft = ( (MappedFeaturePropertyType) pt ).getFeatureTypeReference().getFeatureType(); 810 MappedFeatureType[] substitutions = ft.getConcreteSubstitutions(); 811 if ( substitutions.length > 1 ) { 812 // if feature type has more than one concrete substitution, determine concrete 813 // feature type first 814 String msg = StringTools.concat( 200, "FeatureType '", ft.getName(), 815 "' has more than one concrete ", 816 "substitution. Need to determine ", "feature type table first." ); 817 LOG.logDebug( msg ); 818 LOG.logDebug( "Property: " + pt.getName() ); 819 TableRelation[] tableRelations = pt.getTableRelations(); 820 if ( tableRelations.length == 2 ) { 821 StatementBuffer query = buildFeatureTypeSelect( tableRelations[0], tableRelations[1], 822 resultValues, resultPosMap ); 823 LOG.logDebug( "Feature type (and id) query: '" + query + "'" ); 824 if ( query != null ) { 825 stmt = this.datastore.prepareStatement( this.conn, query ); 826 rs = stmt.executeQuery(); 827 while ( rs.next() ) { 828 String featureTypeName = rs.getString( 1 ); 829 MappedFeatureType concreteFeatureType = ft.getGMLSchema().getFeatureType( 830 featureTypeName ); 831 if ( concreteFeatureType == null ) { 832 msg = StringTools.concat( 200, "Lookup of concrete feature type '", 833 featureTypeName, "' failed: ", 834 " Inconsistent featuretype column!?" ); 835 LOG.logError( msg ); 836 throw new DatastoreException( msg ); 837 } 838 FeatureId fid = extractFeatureId( concreteFeatureType, rs, 2 ); 839 msg = StringTools.concat( 200, "Subfeature '", fid.getAsString(), 840 "' has concrete feature type '", 841 concreteFeatureType.getName(), "'." ); 842 LOG.logDebug( msg ); 843 844 if ( !this.featuresInGeneration.contains( fid ) ) { 845 PropertyPath[] subPropertyPaths = PropertyPathResolver.determineSubPropertyPaths( 846 concreteFeatureType, 847 propertyPaths ); 848 Feature feature = fetchFeature( fid, subPropertyPaths ); 849 if ( feature != null ) { 850 FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, 851 feature ); 852 result.add( property ); 853 } 854 } else { 855 FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, null ); 856 addToFidToPropertyMap( fid, property ); 857 result.add( property ); 858 } 859 } 860 } 861 } else if ( tableRelations.length == 1 ) { 862 StatementBuffer query = buildFeatureTypeSelect( tableRelations[0], resultValues, resultPosMap ); 863 LOG.logDebug( "Feature type (and id) query: '" + query + "'" ); 864 if ( query != null ) { 865 stmt = this.datastore.prepareStatement( this.conn, query ); 866 rs = stmt.executeQuery(); 867 while ( rs.next() ) { 868 String featureTypeName = rs.getString( 1 ); 869 MappedFeatureType concreteFeatureType = ft.getGMLSchema().getFeatureType( 870 featureTypeName ); 871 if ( concreteFeatureType == null ) { 872 msg = StringTools.concat( 200, "Lookup of concrete feature type '", 873 featureTypeName, "' failed: ", 874 " Inconsistent featuretype column!?" ); 875 LOG.logError( msg ); 876 throw new DatastoreException( msg ); 877 } 878 879 FeatureId fid = extractFeatureId( concreteFeatureType, rs, 2 ); 880 881 msg = StringTools.concat( 200, "Subfeature '", fid.getAsString(), 882 "' has concrete feature type '", 883 concreteFeatureType.getName(), "'." ); 884 LOG.logDebug( msg ); 885 886 FeatureProperty property = null; 887 if ( !this.featuresInGeneration.contains( fid ) ) { 888 PropertyPath[] subPropertyPaths = PropertyPathResolver.determineSubPropertyPaths( 889 concreteFeatureType, 890 propertyPaths ); 891 Feature feature = fetchFeature( fid, subPropertyPaths ); 892 if ( feature != null ) { 893 property = FeatureFactory.createFeatureProperty( propertyName, feature ); 894 result.add( property ); 895 } 896 897 } else { 898 property = FeatureFactory.createFeatureProperty( propertyName, null ); 899 addToFidToPropertyMap( fid, property ); 900 result.add( property ); 901 } 902 } 903 } 904 } else { 905 msg = StringTools.concat( 200, "Querying of feature properties ", 906 "with a content type with more than one ", 907 "concrete substitution is not implemented for ", 908 tableRelations.length, " TableRelations." ); 909 throw new DatastoreException( msg ); 910 } 911 } else { 912 // feature type is the only substitutable concrete feature type 913 PropertyPath[] subPropertyPaths = PropertyPathResolver.determineSubPropertyPaths( ft, propertyPaths ); 914 // TODO aliases? 915 Map<MappedPropertyType, Collection<PropertyPath>> requestedPropertiesMap = PropertyPathResolver.determineFetchProperties( 916 ft, 917 null, 918 subPropertyPaths ); 919 MappedPropertyType[] requestedProperties = requestedPropertiesMap.keySet().toArray( 920 new MappedPropertyType[requestedPropertiesMap.size()] ); 921 922 // determine contents (fields / functions) that needs to be SELECTed from 923 // current table 924 List<List<SimpleContent>> fetchContents = determineFetchContents( ft, requestedProperties ); 925 Map<SimpleContent, Integer> newResultPosMap = buildResultPosMap( fetchContents ); 926 927 StatementBuffer query = buildSubsequentSelect( fetchContents, pt.getTableRelations(), resultValues, 928 resultPosMap ); 929 LOG.logDebug( "Subsequent query: '" + query + "'" ); 930 931 if ( query != null ) { 932 Object[] newResultValues = new Object[fetchContents.size()]; 933 stmt = this.datastore.prepareStatement( this.conn, query ); 934 rs = stmt.executeQuery(); 935 while ( rs.next() ) { 936 // cache result values 937 for ( int i = 0; i < newResultValues.length; i++ ) { 938 newResultValues[i] = rs.getObject( i + 1 ); 939 } 940 FeatureId fid = extractFeatureId( ft, newResultPosMap, newResultValues ); 941 FeatureProperty property = null; 942 if ( !this.featuresInGeneration.contains( fid ) ) { 943 Feature feature = extractFeature( fid, requestedPropertiesMap, newResultPosMap, 944 newResultValues ); 945 property = FeatureFactory.createFeatureProperty( propertyName, feature ); 946 } else { 947 property = FeatureFactory.createFeatureProperty( propertyName, null ); 948 addToFidToPropertyMap( fid, property ); 949 } 950 result.add( property ); 951 } 952 } 953 } 954 } else { 955 String msg = "Unsupported content type: '" + pt.getClass().getName() 956 + "' in QueryHandler.fetchRelatedProperties()."; 957 throw new IllegalArgumentException( msg ); 958 } 959 } finally { 960 try { 961 if ( rs != null ) { 962 rs.close(); 963 } 964 if ( stmt != null ) { 965 stmt.close(); 966 } 967 } finally { 968 if ( stmt != null ) { 969 stmt.close(); 970 } 971 } 972 } 973 return result; 974 } 975 976 private void addToFidToPropertyMap( FeatureId fid, FeatureProperty property ) { 977 List<FeatureProperty> properties = this.fidToPropertyMap.get( fid ); 978 if ( properties == null ) { 979 properties = new ArrayList<FeatureProperty>(); 980 this.fidToPropertyMap.put( fid, properties ); 981 } 982 properties.add( property ); 983 } 984 985 protected void appendQualifiedContentList( StatementBuffer query, String tableAlias, 986 List<List<SimpleContent>> fetchContents ) { 987 988 for ( int i = 0; i < fetchContents.size(); i++ ) { 989 SimpleContent content = fetchContents.get( i ).get( 0 ); 990 if ( content instanceof MappingField ) { 991 appendQualifiedColumn( query, tableAlias, ( (MappingField) content ).getField() ); 992 } else if ( content instanceof SQLFunctionCall ) { 993 this.vcProvider.appendSQLFunctionCall( query, tableAlias, (SQLFunctionCall) content ); 994 } else { 995 assert false; 996 } 997 if ( i != fetchContents.size() - 1 ) { 998 query.append( "," ); 999 } 1000 } 1001 } 1002 1003 /** 1004 * Builds a lookup map that allows to find the index (position in the {@link ResultSet}) by the 1005 * {@link SimpleContent} instance that makes it necessary to fetch it. 1006 * 1007 * @param fetchContents 1008 * @return key: SimpleContent, value: Integer (position in ResultSet) 1009 */ 1010 protected Map<SimpleContent, Integer> buildResultPosMap( List<List<SimpleContent>> fetchContents ) { 1011 1012 Map<SimpleContent, Integer> resultPosMap = new HashMap<SimpleContent, Integer>(); 1013 for ( int i = 0; i < fetchContents.size(); i++ ) { 1014 for ( SimpleContent content : fetchContents.get( i ) ) { 1015 resultPosMap.put( content, i ); 1016 } 1017 } 1018 return resultPosMap; 1019 } 1020 }