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