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