001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/io/datastore/sde/SDEQueryHandler.java $ 002 /*---------------- FILE HEADER ------------------------------------------ 003 004 This file is part of deegree. 005 Copyright (C) 2006 by: M.O.S.S. Computer Grafik Systeme GmbH 006 Hohenbrunner Weg 13 007 D-82024 Taufkirchen 008 http://www.moss.de/ 009 010 This library is free software; you can redistribute it and/or 011 modify it under the terms of the GNU Lesser General Public 012 License as published by the Free Software Foundation; either 013 version 2.1 of the License, or (at your option) any later version. 014 015 This library is distributed in the hope that it will be useful, 016 but WITHOUT ANY WARRANTY; without even the implied warranty of 017 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 018 Lesser General Public License for more details. 019 020 You should have received a copy of the GNU Lesser General Public 021 License along with this library; if not, write to the Free Software 022 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 023 024 ---------------------------------------------------------------------------*/ 025 package org.deegree.io.datastore.sde; 026 027 import java.util.ArrayList; 028 import java.util.Collection; 029 import java.util.HashMap; 030 import java.util.HashSet; 031 import java.util.Iterator; 032 import java.util.List; 033 import java.util.Map; 034 import java.util.Set; 035 036 import org.deegree.datatypes.QualifiedName; 037 import org.deegree.framework.log.ILogger; 038 import org.deegree.framework.log.LoggerFactory; 039 import org.deegree.io.datastore.DatastoreException; 040 import org.deegree.io.datastore.FeatureId; 041 import org.deegree.io.datastore.PropertyPathResolver; 042 import org.deegree.io.datastore.PropertyPathResolvingException; 043 import org.deegree.io.datastore.schema.MappedFeatureType; 044 import org.deegree.io.datastore.schema.MappedGeometryPropertyType; 045 import org.deegree.io.datastore.schema.MappedPropertyType; 046 import org.deegree.io.datastore.schema.MappedSimplePropertyType; 047 import org.deegree.io.datastore.schema.TableRelation; 048 import org.deegree.io.datastore.schema.content.MappingField; 049 import org.deegree.io.datastore.schema.content.SimpleContent; 050 import org.deegree.io.datastore.sql.TableAliasGenerator; 051 import org.deegree.io.sdeapi.SDEConnection; 052 import org.deegree.model.feature.Feature; 053 import org.deegree.model.feature.FeatureCollection; 054 import org.deegree.model.feature.FeatureFactory; 055 import org.deegree.model.feature.FeatureProperty; 056 import org.deegree.model.feature.XLinkedFeatureProperty; 057 import org.deegree.model.filterencoding.ComplexFilter; 058 import org.deegree.model.spatialschema.Geometry; 059 import org.deegree.ogcbase.PropertyPath; 060 import org.deegree.ogcwebservices.wfs.operation.Query; 061 import org.deegree.ogcwebservices.wfs.operation.GetFeature.RESULT_TYPE; 062 063 import com.esri.sde.sdk.client.SeFilter; 064 import com.esri.sde.sdk.client.SeLayer; 065 import com.esri.sde.sdk.client.SeObjectId; 066 import com.esri.sde.sdk.client.SeQuery; 067 import com.esri.sde.sdk.client.SeRow; 068 import com.esri.sde.sdk.client.SeSqlConstruct; 069 import com.esri.sde.sdk.client.SeState; 070 071 /** 072 * Special <code>QueryHandler</code> implementation for the <code>SDEDatastore</code>. 073 * 074 * @author <a href="mailto:cpollmann@moss.de">Christoph Pollmann</a> 075 * @author last edited by: $Author: mschneider $ 076 * 077 * @version $Revision: 23718 $ 078 */ 079 public class SDEQueryHandler extends AbstractSDERequestHandler { 080 081 private static final ILogger LOG = LoggerFactory.getLogger( SDEQueryHandler.class ); 082 083 /** 084 * requested feature type 085 */ 086 protected MappedFeatureType rootFeatureType; 087 088 /** 089 * requested properties of the feature type 090 */ 091 protected PropertyPath[] propertyNames; 092 093 /** 094 * used to build the initial SELECT (especially the WHERE-clause) 095 */ 096 protected SDEWhereBuilder whereBuilder; 097 098 /** 099 * key: feature id of features that are generated or are in generation 100 */ 101 protected Set<FeatureId> featuresInGeneration = new HashSet<FeatureId>(); 102 103 /** 104 * key: feature id value, value: Feature 105 */ 106 protected Map<String, Feature> featureMap = new HashMap<String, Feature>( 1000 ); 107 108 /** 109 * value: XLinkedFeatureProperty 110 */ 111 private Collection<XLinkedFeatureProperty> xlinkProperties = new ArrayList<XLinkedFeatureProperty>(); 112 113 private Query query = null; 114 115 /** 116 * Creates a new instance of <code>SDEQueryHandler</code> from the given parameters. 117 * 118 * @param datastore 119 * datastore that spawned this QueryHandler 120 * @param aliasGenerator 121 * used to generate unique aliases for the tables in the SELECT statements 122 * @param conn 123 * SDEConnection to execute the generated SELECT statements against 124 * @param rootFts 125 * queried feature types 126 * @param query 127 * Query to perform 128 * @throws DatastoreException 129 */ 130 public SDEQueryHandler( SDEDatastore datastore, TableAliasGenerator aliasGenerator, SDEConnection conn, 131 MappedFeatureType[] rootFts, Query query ) throws DatastoreException { 132 super( datastore, aliasGenerator, conn ); 133 this.rootFeatureType = rootFts[0]; 134 this.propertyNames = PropertyPathResolver.normalizePropertyPaths( rootFeatureType, null, 135 query.getPropertyNames() ); 136 this.whereBuilder = this.datastore.getWhereBuilder( rootFts, query.getAliases(), query.getFilter(), 137 aliasGenerator ); 138 this.aliasGenerator = aliasGenerator; 139 this.query = query; 140 } 141 142 /** 143 * Performs the associated <code>Query</code> against the datastore. 144 * 145 * @return collection of requested features 146 * @throws DatastoreException 147 */ 148 public FeatureCollection performQuery() 149 throws DatastoreException { 150 151 FeatureCollection result = null; 152 if ( this.query.getResultType() == RESULT_TYPE.HITS ) { 153 // TODO 154 } else { 155 result = performContentQuery(); 156 } 157 158 return result; 159 } 160 161 /** 162 * Performs a query for the feature instances that match the query constraints. This corresponds to a query with 163 * resultType=RESULTS. 164 * 165 * @return a feature collection containing the features that match the query constraints 166 * @throws PropertyPathResolvingException 167 * @throws DatastoreException 168 */ 169 private FeatureCollection performContentQuery() 170 throws PropertyPathResolvingException, DatastoreException { 171 172 FeatureCollection result = FeatureFactory.createFeatureCollection( "ID", 10000 ); 173 String[] columns; 174 // TODO respect alias 175 Map<MappedPropertyType, Collection<PropertyPath>> requestedPropertyMap = PropertyPathResolver.determineFetchProperties( 176 this.rootFeatureType, 177 null, 178 this.propertyNames ); 179 MappedPropertyType[] requestedProperties = new MappedPropertyType[requestedPropertyMap.size()]; 180 requestedProperties = requestedPropertyMap.keySet().toArray( requestedProperties ); 181 182 Map<String, List<MappingField>> columnsMap = buildColumnsMap( this.rootFeatureType, requestedProperties, true ); 183 columns = columnsMap.keySet().toArray( new String[columnsMap.size()] ); 184 Map<MappingField,Integer> mappingFieldsMap = buildMappingFieldMap( columns, columnsMap ); 185 186 SeQuery stmt = buildInitialSelect( columns ); 187 Object[] resultValues = new Object[columns.length]; 188 189 // necessary to handle that a feature may occur several times in result set 190 Set<FeatureId> rootFeatureIds = new HashSet<FeatureId>(); 191 192 try { 193 int maxFeatures = this.query.getMaxFeatures(); 194 int startPosition = this.query.getStartPosition(); 195 int rowCount = 0; 196 stmt.execute(); 197 SeRow row = null; 198 if ( maxFeatures != -1 ) { 199 if ( startPosition < 0 ) { 200 startPosition = 0; 201 } 202 } 203 for ( ;; ) { 204 try { 205 row = stmt.fetch(); 206 } catch ( Exception e ) { 207 row = null; 208 } 209 if ( null == row ) 210 break; 211 rowCount++; 212 if ( rowCount < startPosition ) 213 continue; 214 // collect result values 215 for ( int i = 0; i < resultValues.length; i++ ) { 216 try { 217 resultValues[i] = row.getObject( i ); 218 } catch ( Exception e ) { 219 //never happens??? 220 } 221 } 222 FeatureId fid = extractFeatureId( this.rootFeatureType, mappingFieldsMap, resultValues ); 223 224 // skip it if this root feature has already been fetched 225 if ( !rootFeatureIds.contains( fid ) ) { 226 227 // feature also may have been fetched already as subfeature 228 Feature feature = this.featureMap.get( fid ); 229 if ( feature == null ) { 230 feature = extractFeature( fid, this.rootFeatureType, requestedPropertyMap, mappingFieldsMap, 231 resultValues ); 232 } 233 result.add( feature ); 234 } 235 } 236 } catch( Exception e ){ 237 throw new DatastoreException( e ); 238 }finally { 239 try { 240 if ( stmt != null ) { 241 stmt.close(); 242 } 243 } catch ( Exception exc2 ) { 244 throw new DatastoreException( exc2 ); 245 } 246 } 247 resolveXLinks(); 248 result.setAttribute( "numberOfFeatures", "" + result.size() ); 249 return result; 250 } 251 252 /** 253 * @throws DatastoreException 254 */ 255 protected void resolveXLinks() 256 throws DatastoreException { 257 for( XLinkedFeatureProperty property : this.xlinkProperties ){ 258 Feature feature = this.featureMap.get( property.getTargetFeatureId() ); 259 if ( feature == null ) { 260 throw new DatastoreException( "Internal error in QueryHandler." ); 261 } 262 property.setValue( feature ); 263 } 264 } 265 266 /** 267 * Builds the initial SELECT statement. 268 * <p> 269 * This statement determines all feature ids that are affected by the filter, but also SELECTs all properties that 270 * are stored in the root feature type's table (to improve efficiency). 271 * </p> 272 * <p> 273 * The statement is structured like this: 274 * <ul> 275 * <li><code>SELECT</code></li> 276 * <li>comma-separated list of selected qualified fields</li> 277 * <li><code>FROM</code></li> 278 * <li>comma-separated list of tables and their aliases (this is needed to constrain the paths to selected 279 * XPath-PropertyNames)</li> 280 * <li><code>WHERE</code></li> 281 * <li>SQL representation of the Filter expression</li> 282 * <li><code>ORDER BY</code></li> 283 * <li>qualified sort criteria fields</li> 284 * </ul> 285 * </p> 286 * 287 * @param columns 288 * @return the initial query 289 */ 290 protected SeQuery buildInitialSelect( String[] columns ) { 291 SeQuery query = null; 292 try { 293 StringBuffer whereCondition = new StringBuffer(); 294 whereBuilder.appendWhereCondition( whereCondition ); 295 SeSqlConstruct constr = new SeSqlConstruct( rootFeatureType.getTable(), whereCondition.toString() ); 296 query = new SeQuery( getConnection().getConnection(), columns, constr ); 297 query.setState( getConnection().getState().getId(), new SeObjectId( SeState.SE_NULL_STATE_ID ), 298 SeState.SE_STATE_DIFF_NOCHECK ); 299 300 query.prepareQuery(); 301 302 if ( this.query.getFilter() instanceof ComplexFilter ) { 303 // There is NO chance, to make a new SeCoordinateReference equal to the existing crs 304 // of the requested layer. 305 // So, we give it a chance, by passing the layer definitions (and its associated 306 // crs) to the whereBuilder method 307 List<SeLayer> layers = getConnection().getConnection().getLayers(); 308 SeFilter[] spatialFilter = whereBuilder.buildSpatialFilter( (ComplexFilter) this.query.getFilter(), 309 layers ); 310 if ( null != spatialFilter && 0 < spatialFilter.length ) { 311 query.setSpatialConstraints( SeQuery.SE_OPTIMIZE, false, spatialFilter ); 312 } 313 } 314 } catch ( Exception e ) { 315 e.printStackTrace(); 316 } 317 318 // append sort criteria (simple implementation) 319 // TODO implement sort as comparison operator in feature collection 320 // because fetching of subfeatures can not be handled here 321 // sort only provides ascendig sorting at the moment! 322 /* 323 * TODO!!!! SortProperty[] sortProperties = query.getSortProperties(); if (null != sortProperties && 0 < 324 * sortProperties.length) { String[] sortColumns = new String[sortProperties.length]; for ( int i = 0; i < 325 * sortProperties.length; i++ ) { PropertyPath pp = sortProperties[i].getSortProperty(); PropertyPath npp = 326 * PropertyPathResolver.normalizePropertyPath( rootFeatureType, pp ); QualifiedName propertyName = npp.getStep( 327 * 1 ).getPropertyName(); PropertyType property = rootFeatureType.getProperty( propertyName ); PropertyContent[] 328 * contents = ( (MappedPropertyType) property ).getContents(); if ( contents[0] instanceof SimplePropertyContent ) { 329 * sortColumns[i] = ( (SimplePropertyContent) contents[0] ).getMappingField().getField(); } } querybuf.append(" 330 * ORDER BY "); appendColumnsList( querybuf, sortColumns ); } 331 */ 332 return query; 333 } 334 335 /** 336 * Builds a SELECT statement to fetch features / properties that are stored in a related table. 337 * 338 * @param columns 339 * table column names to fetch 340 * @param relations 341 * table relations that lead to the table where the property is stored 342 * @param resultValues 343 * all retrieved columns from one result set row 344 * @param mappingFieldMap 345 * key class: MappingField, value class: Integer (this is the associated index in resultValues) 346 * @return the statement or null if the keys in resultValues contain NULL values 347 */ 348 private SeQuery buildSubsequentSelect( String[] columns, TableRelation[] relations, Object[] resultValues, 349 Map<MappingField, Integer> mappingFieldMap ) { 350 SeQuery query = null; 351 try { 352 StringBuffer whereCondition = new StringBuffer(); 353 354 // joins can't be used in versioned SDEs (so only one join level possible) 355 356 // append key constraints 357 MappingField[] fromFields = relations[0].getFromFields(); 358 MappingField[] toFields = relations[0].getToFields(); 359 for ( int i = 0; i < fromFields.length; i++ ) { 360 Integer resultPos = mappingFieldMap.get( fromFields[i] ); 361 Object keyValue = resultValues[resultPos.intValue()]; 362 if ( keyValue == null ) { 363 return null; 364 } 365 whereCondition.append( toFields[i].getField() ); 366 whereCondition.append( "='" + keyValue.toString() + "'" ); 367 if ( i != fromFields.length - 1 ) { 368 whereCondition.append( " AND " ); 369 } 370 } 371 SeSqlConstruct constr = new SeSqlConstruct( relations[0].getToTable(), whereCondition.toString() ); 372 373 query = new SeQuery( getConnection().getConnection(), columns, constr ); 374 query.setState( getConnection().getState().getId(), new SeObjectId( SeState.SE_NULL_STATE_ID ), 375 SeState.SE_STATE_DIFF_NOCHECK ); 376 query.prepareQuery(); 377 } catch ( Exception e ) { 378 e.printStackTrace(); 379 } 380 return query; 381 } 382 383 /** 384 * Extracts a feature from the values of a result set row. 385 * 386 * @param fid 387 * feature id of the feature 388 * @param featureType 389 * feature type of the feature to be extracted 390 * @param requestedPropertyMap 391 * requested <code>MappedPropertyType</code>s mapped to <code>Collection</code> of 392 * <code>PropertyPath</code>s 393 * @param mappingFieldsMap 394 * key class: MappingField, value class: Integer (this is the associated index in resultValues) 395 * @param resultValues 396 * all retrieved columns from one result set row 397 * @return the extracted feature 398 * @throws DatastoreException 399 */ 400 protected Feature extractFeature( FeatureId fid, MappedFeatureType featureType, 401 Map<MappedPropertyType, Collection<PropertyPath>> requestedPropertyMap, 402 Map<MappingField,Integer> mappingFieldsMap, Object[] resultValues ) 403 throws DatastoreException { 404 405 this.featuresInGeneration.add( fid ); 406 407 // extract the requested properties of the feature 408 List<FeatureProperty> propertyList = new ArrayList<FeatureProperty>(); 409 Iterator<MappedPropertyType> propertyIter = requestedPropertyMap.keySet().iterator(); 410 while ( propertyIter.hasNext() ) { 411 MappedPropertyType requestedProperty = propertyIter.next(); 412 // PropertyPath[] requestingPaths = PropertyPathResolver.determineSubPropertyPaths 413 // (requestedProperty, propertyPaths); 414 Collection<FeatureProperty> props = extractProperties( requestedProperty, mappingFieldsMap, resultValues ); 415 propertyList.addAll( props ); 416 } 417 FeatureProperty[] properties = propertyList.toArray( new FeatureProperty[propertyList.size()] ); 418 Feature feature = FeatureFactory.createFeature( fid.getAsString(), featureType, properties ); 419 420 this.featureMap.put( fid.getAsString(), feature ); 421 return feature; 422 } 423 424 /** 425 * Extracts the feature id from the values of a result set row. 426 * 427 * @param ft 428 * feature type for which the id shall be extracted 429 * @param mappingFieldMap 430 * key class: MappingField, value class: Integer (this is the associated index in resultValues) 431 * @param resultValues 432 * all retrieved columns from one result set row 433 * @return the feature id 434 */ 435 protected FeatureId extractFeatureId( MappedFeatureType ft, Map<MappingField, Integer> mappingFieldMap, Object[] resultValues ) { 436 MappingField[] idFields = ft.getGMLId().getIdFields(); 437 Object[] idValues = new Object[idFields.length]; 438 for ( int i = 0; i < idFields.length; i++ ) { 439 Integer resultPos = mappingFieldMap.get( idFields[i] ); 440 idValues[i] = resultValues[resultPos.intValue()]; 441 } 442 return new FeatureId( ft, idValues ); 443 } 444 445 /** 446 * Extracts the properties of the given property type from the values of a result set row. If the property is stored 447 * in related table, only the key values are present in the result set row and more SELECTs are built and executed 448 * to build the property. 449 * <p> 450 * FYI: If the property is not stored in a related table, only one FeatureProperty is built, otherwise the number of 451 * properties depends on the multiplicity of the relation. 452 * 453 * @param propertyType 454 * the mapped property type to be extracted 455 * @param mappingFieldMap 456 * key class: MappingField, value class: Integer (this is the associated index in resultValues) 457 * @param resultValues 458 * all retrieved columns from one result set row 459 * @return Collection of FeatureProperty instances 460 * 461 * @throws DatastoreException 462 */ 463 private Collection<FeatureProperty> extractProperties( MappedPropertyType propertyType, 464 Map<MappingField, Integer> mappingFieldMap, 465 Object[] resultValues ) 466 throws DatastoreException { 467 Collection<FeatureProperty> result = null; 468 469 TableRelation[] tableRelations = propertyType.getTableRelations(); 470 if ( tableRelations != null && tableRelations.length != 0 ) { 471 LOG.logDebug( "Fetching related properties: '" + propertyType.getName() + "'..." ); 472 result = fetchRelatedProperties( propertyType.getName(), propertyType, mappingFieldMap, resultValues ); 473 } else { 474 Object propertyValue = null; 475 if ( propertyType instanceof MappedSimplePropertyType ) { 476 SimpleContent content = ( (MappedSimplePropertyType) propertyType ).getContent(); 477 if ( content instanceof MappingField ) { 478 MappingField field = (MappingField) content; 479 Integer resultPos = mappingFieldMap.get( field ); 480 propertyValue = resultValues[resultPos.intValue()]; 481 } 482 } else if ( propertyType instanceof MappedGeometryPropertyType ) { 483 MappedGeometryPropertyType geomPropertyType = (MappedGeometryPropertyType) propertyType; 484 MappingField field = geomPropertyType.getMappingField(); 485 Integer resultPos = mappingFieldMap.get( field ); 486 propertyValue = resultValues[resultPos.intValue()]; 487 propertyValue = this.datastore.convertDBToDegreeGeometry( propertyValue, geomPropertyType.getCS() ); 488 } else { 489 String msg = "Unsupported property type: '" + propertyType.getClass().getName() 490 + "' in QueryHandler.extractProperties(). "; 491 LOG.logError( msg ); 492 throw new IllegalArgumentException( msg ); 493 } 494 FeatureProperty property = FeatureFactory.createFeatureProperty( propertyType.getName(), propertyValue ); 495 result = new ArrayList<FeatureProperty>(); 496 result.add( property ); 497 } 498 return result; 499 } 500 501 /** 502 * 503 * @param propertyName 504 * @param pt 505 * @param mappingFieldMap 506 * key class: MappingField, value class: Integer (this is the associated index in resultValues) 507 * @param resultValues 508 * all retrieved columns from one result set row 509 * @return Collection of FeatureProperty instances 510 * @throws DatastoreException 511 */ 512 private Collection<FeatureProperty> fetchRelatedProperties( QualifiedName propertyName, MappedPropertyType pt, 513 Map<MappingField, Integer> mappingFieldMap, Object[] resultValues ) throws DatastoreException { 514 Collection<FeatureProperty> result = new ArrayList<FeatureProperty>( 100 ); 515 SeQuery stmt = null; 516 SeRow row = null; 517 try { 518 if ( pt instanceof MappedSimplePropertyType ) { 519 520 SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent(); 521 if ( content instanceof MappingField ) { 522 String column = ( (MappingField) content ).getField(); 523 stmt = buildSubsequentSelect( new String[] { column }, pt.getTableRelations(), resultValues, 524 mappingFieldMap ); 525 if ( stmt != null ) { 526 stmt.execute(); 527 for ( ;; ) { 528 try { 529 row = stmt.fetch(); 530 } catch ( Exception e ) { 531 row = null; 532 } 533 if ( null == row ) 534 break; 535 Object propertyValue = row.getObject( 0 ); 536 FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, 537 propertyValue ); 538 result.add( property ); 539 } 540 } 541 } 542 } else if ( pt instanceof MappedGeometryPropertyType ) { 543 MappedGeometryPropertyType geomPropertyType = (MappedGeometryPropertyType) pt; 544 String column = geomPropertyType.getMappingField().getField(); 545 546 stmt = buildSubsequentSelect( new String[] { column }, pt.getTableRelations(), resultValues, 547 mappingFieldMap ); 548 if ( stmt != null ) { 549 stmt.execute(); 550 for ( ;; ) { 551 try { 552 row = stmt.fetch(); 553 } catch ( Exception e ) { 554 row = null; 555 } 556 if ( null == row ) 557 break; 558 Object value = row.getObject( 0 ); 559 Geometry geometry = this.datastore.convertDBToDegreeGeometry( value, geomPropertyType.getCS() ); 560 FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, geometry ); 561 result.add( property ); 562 } 563 } 564 } else { 565 String msg = "Unsupported content type: '" + pt.getClass().getName() 566 + "' in QueryHandler.fetchRelatedProperties()."; 567 LOG.logError( msg ); 568 throw new DatastoreException( msg ); 569 } 570 } catch ( Exception exc ) { 571 throw new DatastoreException( exc ); 572 } finally { 573 try { 574 if ( stmt != null ) { 575 stmt.close(); 576 } 577 } catch ( Exception exc2 ) { 578 throw new DatastoreException( exc2 ); 579 } 580 } 581 return result; 582 } 583 }