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