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