001 //$HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/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 }