//$Header: /home/deegree/jail/deegreerepository/deegree/src/org/deegree/io/datastore/sql/FeatureFetcher.java,v 1.6 2006/11/29 16:59:54 mschneider Exp $ /*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001-2006 by: Department of Geography, University of Bonn http://www.giub.uni-bonn.de/deegree/ lat/lon GmbH http://www.lat-lon.de This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Andreas Poth lat/lon GmbH Aennchenstraße 19 53177 Bonn Germany E-Mail: poth@lat-lon.de Prof. Dr. Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: greve@giub.uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.io.datastore.sql; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.deegree.datatypes.QualifiedName; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.i18n.Messages; import org.deegree.io.datastore.DatastoreException; import org.deegree.io.datastore.FeatureId; import org.deegree.io.datastore.PropertyPathResolver; import org.deegree.io.datastore.schema.MappedFeaturePropertyType; import org.deegree.io.datastore.schema.MappedFeatureType; import org.deegree.io.datastore.schema.MappedGMLId; import org.deegree.io.datastore.schema.MappedGeometryPropertyType; import org.deegree.io.datastore.schema.MappedPropertyType; import org.deegree.io.datastore.schema.MappedSimplePropertyType; import org.deegree.io.datastore.schema.TableRelation; import org.deegree.io.datastore.schema.content.ConstantContent; import org.deegree.io.datastore.schema.content.MappingField; import org.deegree.io.datastore.schema.content.MappingGeometryField; import org.deegree.io.datastore.schema.content.SQLFunctionCall; import org.deegree.io.datastore.schema.content.SimpleContent; import org.deegree.model.crs.CRSFactory; import org.deegree.model.crs.CoordinateSystem; import org.deegree.model.crs.UnknownCRSException; import org.deegree.model.feature.Feature; import org.deegree.model.feature.FeatureFactory; import org.deegree.model.feature.FeatureProperty; import org.deegree.model.feature.XLinkedFeatureProperty; import org.deegree.model.feature.schema.FeatureType; import org.deegree.model.feature.schema.PropertyType; import org.deegree.model.spatialschema.Geometry; import org.deegree.ogcbase.PropertyPath; import org.deegree.ogcwebservices.wfs.operation.Query; /** * The only implementation of this abstract class is the {@link QueryHandler} class. * <p> * While the {@link QueryHandler} class performs the initial SELECT, {@link FeatureFetcher} * is responsible for any subsequent SELECTs that may be necessary. * * @see QueryHandler * * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a> * @author last edited by: $Author: mschneider $ * * @version $Revision: 1.6 $, $Date: 2006/11/29 16:59:54 $ */ abstract class FeatureFetcher extends AbstractRequestHandler { private static final ILogger LOG = LoggerFactory.getLogger( FeatureFetcher.class ); // key: feature id of features that are generated or are in generation protected Set<FeatureId> featuresInGeneration = new HashSet<FeatureId>(); // key: feature id value, value: Feature protected Map<String, Feature> featureMap = new HashMap<String, Feature>( 1000 ); // provides virtual content (constants, sql functions, ...) protected VirtualContentProvider vcProvider; // value: XLinkedFeatureProperty protected Collection<XLinkedFeatureProperty> xlinkProperties = new ArrayList<XLinkedFeatureProperty>(); protected Query query; // TODO do this a better way!!! private Map<MappingGeometryField, SimpleContent> geometryFieldMap = new HashMap<MappingGeometryField, SimpleContent>(); FeatureFetcher( AbstractSQLDatastore datastore, TableAliasGenerator aliasGenerator, Connection conn, Query query ) { super( datastore, aliasGenerator, conn ); this.query = query; } /** * Builds a SELECT statement to fetch features / properties that are stored in a related table. * * @param fetchContents * table columns / functions to fetch * @param relations * table relations that lead to the table where the property is stored * @param resultValues * all retrieved columns from one result set row * @param resultPosMap * key class: SimpleContent, value class: Integer (this is the associated index in * resultValues) * @return the statement or null if the keys in resultValues contain NULL values */ private StatementBuffer buildSubsequentSelect( List<List<SimpleContent>> fetchContents, TableRelation[] relations, Object[] resultValues, Map<SimpleContent, Integer> resultPosMap ) { this.aliasGenerator.reset(); String[] tableAliases = this.aliasGenerator.generateUniqueAliases( relations.length ); StatementBuffer query = new StatementBuffer(); query.append( "SELECT " ); appendQualifiedContentList( query, tableAliases[tableAliases.length - 1], fetchContents ); query.append( " FROM " ); query.append( relations[0].getToTable() ); query.append( " " ); query.append( tableAliases[0] ); // append joins for ( int i = 1; i < relations.length; i++ ) { query.append( " JOIN " ); query.append( relations[i].getToTable() ); query.append( " " ); query.append( tableAliases[i] ); query.append( " ON " ); MappingField[] fromFields = relations[i].getFromFields(); MappingField[] toFields = relations[i].getToFields(); for ( int j = 0; j < fromFields.length; j++ ) { query.append( tableAliases[i - 1] ); query.append( '.' ); query.append( fromFields[j].getField() ); query.append( '=' ); query.append( tableAliases[i] ); query.append( '.' ); query.append( toFields[j].getField() ); } } // append key constraints query.append( " WHERE " ); MappingField[] fromFields = relations[0].getFromFields(); MappingField[] toFields = relations[0].getToFields(); for ( int i = 0; i < fromFields.length; i++ ) { int resultPos = resultPosMap.get( fromFields[i] ); Object keyValue = resultValues[resultPos]; if ( keyValue == null ) { return null; } query.append( tableAliases[0] ); query.append( '.' ); query.append( toFields[i].getField() ); query.append( "=?" ); query.addArgument( keyValue, toFields[i].getType() ); if ( i != fromFields.length - 1 ) { query.append( " AND " ); } } return query; } /** * Builds a SELECT statement to fetch the feature ids and the (concrete) feature types of feature properties * that are stored in a related table (currently limited to *one* join table). * <p> * This is only necessary for feature properties that contain feature types with more than one possible * substitution. * * @param relation1 * first table relation that leads to the join table * @param relation2 * second table relation that leads to the table where the property is stored * @param resultValues * all retrieved columns from one result set row * @param mappingFieldMap * key class: MappingField, value class: Integer (this is the associated index in * resultValues) * @return the statement or null if the keys in resultValues contain NULL values */ private StatementBuffer buildFeatureTypeSelect( TableRelation relation1, TableRelation relation2, Object[] resultValues, Map mappingFieldMap ) { StatementBuffer query = new StatementBuffer(); query.append( "SELECT " ); // append feature type column query.append( FT_COLUMN ); // append feature id columns MappingField[] fidFields = relation2.getFromFields(); for ( int i = 0; i < fidFields.length; i++ ) { query.append( ',' ); query.append( fidFields[i].getField() ); } query.append( " FROM " ); query.append( relation1.getToTable() ); query.append( " WHERE " ); // append key constraints MappingField[] fromFields = relation1.getFromFields(); MappingField[] toFields = relation1.getToFields(); for ( int i = 0; i < fromFields.length; i++ ) { Integer resultPos = (Integer) mappingFieldMap.get( fromFields[i] ); Object keyValue = resultValues[resultPos.intValue()]; if ( keyValue == null ) { return null; } query.append( toFields[i].getField() ); query.append( "=?" ); query.addArgument( keyValue, toFields[i].getType() ); if ( i != fromFields.length - 1 ) { query.append( " AND " ); } } return query; } /** * Builds a SELECT statement that fetches one feature and it's properties. * * @param fid * id of the feature to fetch * @param table * root table of the feature * @param fetchContents * @return the statement or null if the keys in resultValues contain NULL values */ private StatementBuffer buildFeatureSelect( FeatureId fid, String table, List<List<SimpleContent>> fetchContents ) { StatementBuffer query = new StatementBuffer(); query.append( "SELECT " ); appendQualifiedContentList( query, table, fetchContents ); query.append( " FROM " ); query.append( table ); query.append( " WHERE " ); // append feature id constraints MappingField[] fidFields = fid.getFidDefinition().getIdFields(); for ( int i = 0; i < fidFields.length; i++ ) { query.append( fidFields[i].getField() ); query.append( "=?" ); query.addArgument( fid.getValue( i ), fidFields[i].getType() ); if ( i != fidFields.length - 1 ) { query.append( " AND " ); } } return query; } /** * Extracts a feature from the values of a result set row. * * @param fid * feature id of the feature * @param featureType * feature type of the feature to be extracted * @param requestedPropertyMap * requested <code>MappedPropertyType</code>s mapped to <code>Collection</code> * of <code>PropertyPath</code>s * @param resultPosMap * key class: MappingField, value class: Integer (this is the associated index in * resultValues) * @param resultValues * all retrieved columns from one result set row * @return the extracted feature * @throws SQLException * if a JDBC related error occurs * @throws DatastoreException * @throws UnknownCRSException */ protected Feature extractFeature( FeatureId fid, MappedFeatureType featureType, Map<MappedPropertyType, Collection<PropertyPath>> requestedPropertyMap, Map<SimpleContent, Integer> resultPosMap, Object[] resultValues ) throws SQLException, DatastoreException, UnknownCRSException { this.featuresInGeneration.add( fid ); // extract the requested properties of the feature List<FeatureProperty> propertyList = new ArrayList<FeatureProperty>(); for ( MappedPropertyType requestedProperty : requestedPropertyMap.keySet() ) { Collection<PropertyPath> propertyPaths = requestedPropertyMap.get( requestedProperty ); // PropertyPath[] requestingPaths = PropertyPathResolver.determineSubPropertyPaths // (requestedProperty, propertyPaths); Collection<FeatureProperty> props = extractProperties( requestedProperty, propertyPaths, resultPosMap, resultValues ); propertyList.addAll( props ); } FeatureProperty[] properties = propertyList.toArray( new FeatureProperty[propertyList.size()] ); Feature feature = FeatureFactory.createFeature( fid.getAsString(), featureType, properties ); this.featureMap.put( fid.getAsString(), feature ); return feature; } /** * Extracts the feature id from the values of a result set row. * * @param featureType * feature type for which the id shall be extracted * @param mfMap * key class: MappingField, value class: Integer (this is the associated index in * resultValues) * @param resultValues * all retrieved columns from one result set row * @return the feature id */ protected FeatureId extractFeatureId( MappedFeatureType featureType, Map<SimpleContent, Integer> mfMap, Object[] resultValues ) { MappingField[] idFields = featureType.getGMLId().getIdFields(); Object[] idValues = new Object[idFields.length]; for ( int i = 0; i < idFields.length; i++ ) { Integer resultPos = mfMap.get( idFields[i] ); idValues[i] = resultValues[resultPos.intValue()]; } return new FeatureId( featureType.getGMLId(), idValues ); } /** * Extracts the properties of the given property type from the values of a result set row. If * the property is stored in related table, only the key values are present in the result set * row and more SELECTs are built and executed to build the property. * <p> * FYI: If the property is not stored in a related table, only one FeatureProperty is built, * otherwise the number of properties depends on the multiplicity of the relation. * * @param pt * the mapped property type to be extracted * @param propertyPaths * property paths that refer to the property to be extracted * @param resultPosMap * key class: SimpleContent, value class: Integer (this is the associated index in * resultValues) * @param resultValues * all retrieved columns from one result set row * @return Collection of FeatureProperty instances * @throws SQLException * if a JDBC related error occurs * @throws DatastoreException * @throws UnknownCRSException */ private Collection<FeatureProperty> extractProperties( MappedPropertyType pt, Collection<PropertyPath> propertyPaths, Map<SimpleContent, Integer> resultPosMap, Object[] resultValues ) throws SQLException, DatastoreException, UnknownCRSException { Collection<FeatureProperty> result = null; TableRelation[] tableRelations = pt.getTableRelations(); if ( tableRelations != null && tableRelations.length != 0 ) { LOG.logDebug( "Fetching related properties: '" + pt.getName() + "'..." ); result = fetchRelatedProperties( pt.getName(), pt, propertyPaths, resultPosMap, resultValues ); } else { Object propertyValue = null; if ( pt instanceof MappedSimplePropertyType ) { SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent(); if ( content instanceof MappingField ) { Integer resultPos = resultPosMap.get( content ); propertyValue = resultValues[resultPos.intValue()]; } else if ( content instanceof ConstantContent ) { propertyValue = ( (ConstantContent) content ).getValue(); } else if ( content instanceof SQLFunctionCall ) { Integer resultPos = resultPosMap.get( content ); propertyValue = resultValues[resultPos.intValue()]; } } else if ( pt instanceof MappedGeometryPropertyType ) { // TODO resultPosMap may contain SQLFunctionCall instead of MappingField!!! MappingField field = ( (MappedGeometryPropertyType) pt ).getMappingField(); SimpleContent content = this.geometryFieldMap.get( field ); Integer resultPos = resultPosMap.get( content ); propertyValue = resultValues[resultPos.intValue()]; CoordinateSystem cs = ( (MappedGeometryPropertyType) pt ).getCS(); if ( this.query.getSrsName() != null ) { cs = CRSFactory.create( this.query.getSrsName() ); } propertyValue = this.datastore.convertDBToDeegreeGeometry( propertyValue, cs, conn ); } else { String msg = "Unsupported property type: '" + pt.getClass().getName() + "' in QueryHandler.extractProperties(). "; LOG.logError( msg ); throw new IllegalArgumentException( msg ); } FeatureProperty property = FeatureFactory.createFeatureProperty( pt.getName(), propertyValue ); result = new ArrayList<FeatureProperty>(); result.add( property ); } return result; } /** * Extracts a {@link FeatureId} from one result set row. * * @param featureType * @param rs * @param startIndex * @return feature id from result set row * @throws SQLException */ private FeatureId extractFeatureId( MappedFeatureType featureType, ResultSet rs, int startIndex ) throws SQLException { MappedGMLId gmlId = featureType.getGMLId(); MappingField[] idFields = gmlId.getIdFields(); Object[] idValues = new Object[idFields.length]; for ( int i = 0; i < idValues.length; i++ ) { idValues[i] = rs.getObject( i + startIndex ); } return new FeatureId( gmlId, idValues ); } /** * Determines the columns / functions that have to be fetched from the table of the givens * {@link MappedFeatureType} and associates identical columns / functions to avoid that the * same column / function is SELECTed more than once. Identical columns are put into the same * (inner) list. * <p> * The following {@link SimpleContent} instances of the {@link MappedFeatureType}s annotation * are used to build the list: * <ul> * <li>MappingFields from the wfs:gmlId - annotation element of the feature type definition</li> * <li>MappingFields in the annotations of the property element definitions; if the property's * content is stored in a related table, the MappingFields used in the first wfs:Relation * element's wfs:From element are considered</li> * <li>SQLFunctionCalls in the annotations of the property element definitions</li> * </ul> * * @param ft * feature type for which the content list is built * @param requestedProps * requested properties * @return key class: String (column names), value class: List (containing MappingField * instances) * @throws DatastoreException */ protected List<List<SimpleContent>> determineFetchContents( MappedFeatureType ft, PropertyType[] requestedProps ) throws DatastoreException { List<List<SimpleContent>> fetchList = new ArrayList<List<SimpleContent>>(); // helper lookup map (column names -> referencing MappingField instances) Map<String, List<SimpleContent>> columnsMap = new HashMap<String, List<SimpleContent>>(); // add table columns that are necessary to build the feature's gml id MappingField[] idFields = ft.getGMLId().getIdFields(); for ( int i = 0; i < idFields.length; i++ ) { List<SimpleContent> mappingFieldList = columnsMap.get( idFields[i].getField() ); if ( mappingFieldList == null ) { mappingFieldList = new ArrayList<SimpleContent>(); } mappingFieldList.add( idFields[i] ); columnsMap.put( idFields[i].getField(), mappingFieldList ); } // add columns that are necessary to build the requested feature properties for ( int i = 0; i < requestedProps.length; i++ ) { MappedPropertyType pt = (MappedPropertyType) requestedProps[i]; TableRelation[] tableRelations = pt.getTableRelations(); if ( tableRelations != null && tableRelations.length != 0 ) { // if property is not stored in feature type's table, retrieve key fields of // the first relation's 'From' element MappingField[] fields = tableRelations[0].getFromFields(); for ( int k = 0; k < fields.length; k++ ) { List<SimpleContent> list = columnsMap.get( fields[k].getField() ); if ( list == null ) { list = new ArrayList<SimpleContent>(); } list.add( fields[k] ); columnsMap.put( fields[k].getField(), list ); } // if (content instanceof FeaturePropertyContent) { // if (tableRelations.length == 1) { // // if feature property contains an abstract feature type, retrieve // // feature type as well (stored in column named "FT_fk") // MappedFeatureType subFeatureType = ( (FeaturePropertyContent) content ) // .getFeatureTypeReference().getFeatureType(); // if (subFeatureType.isAbstract()) { // String typeColumn = FT_PREFIX + fields [0].getField(); // columnsMap.put (typeColumn, new ArrayList ()); // } // } // } } else { String column = null; SimpleContent content = null; if ( pt instanceof MappedSimplePropertyType ) { content = ( (MappedSimplePropertyType) pt ).getContent(); if ( content instanceof MappingField ) { column = ( (MappingField) content ).getField(); } else { // ignore virtual properties here continue; } } else if ( pt instanceof MappedGeometryPropertyType ) { content = determineFetchContent( (MappedGeometryPropertyType) pt, query.getSrsName() ); column = ( (MappedGeometryPropertyType) pt ).getMappingField().getField(); } else { assert false; } List<SimpleContent> contentList = columnsMap.get( column ); if ( contentList == null ) { contentList = new ArrayList<SimpleContent>(); } contentList.add( content ); columnsMap.put( column, contentList ); } } fetchList.addAll( columnsMap.values() ); // add functions that are necessary to build the requested feature properties for ( int i = 0; i < requestedProps.length; i++ ) { MappedPropertyType pt = (MappedPropertyType) requestedProps[i]; if ( pt instanceof MappedSimplePropertyType ) { SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent(); if ( content instanceof SQLFunctionCall ) { List<SimpleContent> functionCallList = new ArrayList<SimpleContent>( 1 ); functionCallList.add( content ); fetchList.add( functionCallList ); } else { // ignore other content types here continue; } } } return fetchList; } /** * Determines a {@link SimpleContent} object that represents the queried * {@link MappedGeometryProperty} in the requested SRS. * <p> * <ul> * <li>If the query SRS is identical to the geometry field's SRS (and thus the SRS of the * stored geometry, the corresponding {@link MappingGeometryField} is returned.</li> * <li>If the query SRS differs from the geometry field's SRS (and thus the SRS of the * stored geometry, an {@link SQLFunctionCall} is returned that refers to the stored geometry, * in the query SRS.</li> * </ul> * * @param pt * geometry property * @param queriedSRS * name of the queried SRS (may be null) * @return a <code>SimpleContent</code> instance that represents the queried geometry property * @throws DatastoreException if the transform call cannot be generated */ private SimpleContent determineFetchContent( MappedGeometryPropertyType pt, String queriedSRS ) throws DatastoreException { SimpleContent content = null; MappingGeometryField field = pt.getMappingField(); queriedSRS = this.datastore.checkTransformation( pt, queriedSRS ); if ( queriedSRS != null ) { content = this.datastore.buildSRSTransformCall( pt, queriedSRS ); } else { content = field; } // TODO this.geometryFieldMap.put( field, content ); return content; } /** * Retrieves the feature with the given (concrete) type and feature id. * * @param ft * @param requestedPaths * @param fid * @return the feature with the given type and feature id, may be null * @throws SQLException * @throws DatastoreException * @throws UnknownCRSException */ private Feature fetchFeature( MappedFeatureType ft, PropertyPath[] requestedPaths, FeatureId fid ) throws SQLException, DatastoreException, UnknownCRSException { Feature feature = null; Map<MappedPropertyType, Collection<PropertyPath>> requestedPropMap = PropertyPathResolver.determineFetchProperties( ft, requestedPaths ); MappedPropertyType[] requestedProps = requestedPropMap.keySet().toArray( new MappedPropertyType[requestedPropMap.size()] ); if ( requestedProps.length > 0 ) { // determine contents (fields / functions) that must be SELECTed from root table List<List<SimpleContent>> fetchContents = determineFetchContents( ft, requestedProps ); Map<SimpleContent, Integer> resultPosMap = buildResultPosMap( fetchContents ); // build feature query StatementBuffer query = buildFeatureSelect( fid, ft.getTable(), fetchContents ); LOG.logDebug( "Feature query: '" + query + "'" ); Object[] resultValues = new Object[fetchContents.size()]; PreparedStatement stmt = null; ResultSet rs = null; try { stmt = this.datastore.prepareStatement( this.conn, query ); rs = stmt.executeQuery(); if ( rs.next() ) { // collect result values for ( int i = 0; i < resultValues.length; i++ ) { resultValues[i] = rs.getObject( i + 1 ); } feature = extractFeature( fid, ft, requestedPropMap, resultPosMap, resultValues ); } else { String msg = Messages.getMessage( "DATASTORE_FEATURE_QUERY_NO_RESULT", query.getQueryString() ); LOG.logError( msg ); throw new DatastoreException( msg ); } if ( rs.next() ) { String msg = Messages.getMessage( "DATASTORE_FEATURE_QUERY_MORE_THAN_ONE_RESULT", query.getQueryString() ); LOG.logError( msg ); throw new DatastoreException( msg ); } } finally { try { if ( rs != null ) { rs.close(); } } finally { if ( stmt != null ) { stmt.close(); } } } } return feature; } /** * * @param propertyName * @param pt * @param propertyPaths * property paths that refer to the property to be extracted * @param resultPosMap * key class: MappingField, value class: Integer (this is the associated index in * resultValues) * @param resultValues * all retrieved columns from one result set row * @return Collection of FeatureProperty instances * @throws SQLException * if a JDBC related error occurs * @throws DatastoreException * @throws UnknownCRSException */ private Collection<FeatureProperty> fetchRelatedProperties( QualifiedName propertyName, MappedPropertyType pt, Collection<PropertyPath> propertyPaths, Map<SimpleContent, Integer> resultPosMap, Object[] resultValues ) throws SQLException, DatastoreException, UnknownCRSException { Collection<FeatureProperty> result = new ArrayList<FeatureProperty>( 100 ); PreparedStatement stmt = null; ResultSet rs = null; try { if ( pt instanceof MappedSimplePropertyType ) { SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent(); // TODO check for invalid content types List<SimpleContent> fetchContents = new ArrayList<SimpleContent>( 1 ); List<List<SimpleContent>> fetchContentsList = new ArrayList<List<SimpleContent>>( 1 ); fetchContents.add( content ); fetchContentsList.add( fetchContents ); StatementBuffer query = buildSubsequentSelect( fetchContentsList, pt.getTableRelations(), resultValues, resultPosMap ); LOG.logDebug( "Subsequent query: '" + query + "'" ); if ( query != null ) { stmt = this.datastore.prepareStatement( this.conn, query ); rs = stmt.executeQuery(); while ( rs.next() ) { Object propertyValue = rs.getObject( 1 ); FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, propertyValue ); result.add( property ); } } } else if ( pt instanceof MappedGeometryPropertyType ) { MappingField content = ( (MappedGeometryPropertyType) pt ).getMappingField(); CoordinateSystem cs = ( (MappedGeometryPropertyType) pt ).getCS(); List<SimpleContent> fetchContents = new ArrayList<SimpleContent>( 1 ); List<List<SimpleContent>> fetchContentsList = new ArrayList<List<SimpleContent>>( 1 ); fetchContents.add( content ); fetchContentsList.add( fetchContents ); StatementBuffer query = buildSubsequentSelect( fetchContentsList, pt.getTableRelations(), resultValues, resultPosMap ); LOG.logDebug( "Subsequent query: '" + query + "'" ); if ( query != null ) { stmt = this.datastore.prepareStatement( this.conn, query ); rs = stmt.executeQuery(); while ( rs.next() ) { Object value = rs.getObject( 1 ); Geometry geometry = this.datastore.convertDBToDeegreeGeometry( value, cs, this.conn ); FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, geometry ); result.add( property ); } } } else if ( pt instanceof MappedFeaturePropertyType ) { MappedFeatureType featureType = ( (MappedFeaturePropertyType) pt ).getFeatureTypeReference().getFeatureType(); FeatureType[] substitutions = featureType.getGMLSchema().getSubstitutions( featureType ); if ( substitutions.length > 1 ) { // if feature type has more than one concrete substitution, determine concrete // feature type first LOG.logDebug( "FeatureType '" + featureType.getName() + "' has more than one concrete substitution. Need to determine feature type " + "table first." ); TableRelation[] tableRelations = pt.getTableRelations(); if ( tableRelations.length == 2 ) { StatementBuffer query = buildFeatureTypeSelect( tableRelations[0], tableRelations[1], resultValues, resultPosMap ); LOG.logDebug( "Feature type (and id) query: '" + query + "'" ); if ( query != null ) { stmt = this.datastore.prepareStatement( this.conn, query ); rs = stmt.executeQuery(); while ( rs.next() ) { String featureTypeName = rs.getString( 1 ); MappedFeatureType concreteFeatureType = (MappedFeatureType) featureType.getGMLSchema().getFeatureType( featureTypeName ); if ( concreteFeatureType == null ) { String msg = "Lookup of concrete feature type '" + featureTypeName + "' failed: " + " Inconsistent featuretype column!?"; LOG.logError( msg ); throw new DatastoreException( msg ); } FeatureId fid = extractFeatureId( concreteFeatureType, rs, 2 ); LOG.logDebug( "Subfeature '" + fid.getAsString() + "' has concrete feature type '" + concreteFeatureType.getName() + "'." ); if ( !this.featuresInGeneration.contains( fid ) ) { PropertyPath[] subPropertyPaths = PropertyPathResolver.determineSubPropertyPaths( concreteFeatureType, propertyPaths ); Feature feature = fetchFeature( concreteFeatureType, subPropertyPaths, fid ); if ( feature != null ) { FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, feature ); result.add( property ); } } else { XLinkedFeatureProperty property = new XLinkedFeatureProperty( propertyName, fid.getAsString() ); this.xlinkProperties.add( property ); result.add( property ); } } } } else { String msg = "Querying of feature properties with a content type with more " + "than one concrete substitution is not implemented for " + tableRelations.length + " TableRelations."; LOG.logError( msg ); throw new DatastoreException( msg ); } } else { // feature type is the only substitutable concrete feature type PropertyPath[] subPropertyPaths = PropertyPathResolver.determineSubPropertyPaths( featureType, propertyPaths ); Map<MappedPropertyType, Collection<PropertyPath>> requestedPropertiesMap = PropertyPathResolver.determineFetchProperties( featureType, subPropertyPaths ); MappedPropertyType[] requestedProperties = requestedPropertiesMap.keySet().toArray( new MappedPropertyType[requestedPropertiesMap.size()] ); // determine contents (fields / functions) that needs to be SELECTed from current table List<List<SimpleContent>> fetchContents = determineFetchContents( featureType, requestedProperties ); Map<SimpleContent, Integer> newResultPosMap = buildResultPosMap( fetchContents ); StatementBuffer query = buildSubsequentSelect( fetchContents, pt.getTableRelations(), resultValues, resultPosMap ); LOG.logDebug( "Subsequent query: '" + query + "'" ); if ( query != null ) { Object[] newResultValues = new Object[fetchContents.size()]; stmt = this.datastore.prepareStatement( this.conn, query ); rs = stmt.executeQuery(); while ( rs.next() ) { // cache result values for ( int i = 0; i < newResultValues.length; i++ ) { newResultValues[i] = rs.getObject( i + 1 ); } FeatureId fid = extractFeatureId( featureType, newResultPosMap, newResultValues ); FeatureProperty property = null; Feature feature = null; if ( !this.featuresInGeneration.contains( fid ) ) { feature = extractFeature( fid, featureType, requestedPropertiesMap, newResultPosMap, newResultValues ); property = FeatureFactory.createFeatureProperty( propertyName, feature ); } else { property = new XLinkedFeatureProperty( propertyName, fid.getAsString() ); this.xlinkProperties.add( (XLinkedFeatureProperty) property ); } result.add( property ); } } } } else { String msg = "Unsupported content type: '" + pt.getClass().getName() + "' in QueryHandler.fetchRelatedProperties()."; LOG.logError( msg ); throw new IllegalArgumentException( msg ); } } finally { try { if ( rs != null ) { rs.close(); } if ( stmt != null ) { stmt.close(); } } finally { if ( stmt != null ) { stmt.close(); } } } return result; } protected void appendQualifiedContentList( StatementBuffer query, String tableAlias, List<List<SimpleContent>> fetchContents ) { for ( int i = 0; i < fetchContents.size(); i++ ) { SimpleContent content = fetchContents.get( i ).get( 0 ); if ( content instanceof MappingField ) { appendQualifiedColumn( query, tableAlias, ( (MappingField) content ).getField() ); } else if ( content instanceof SQLFunctionCall ) { this.vcProvider.appendSQLFunctionCall( query, tableAlias, (SQLFunctionCall) content ); } else { assert false; } if ( i != fetchContents.size() - 1 ) { query.append( "," ); } } } /** * Builds a lookup map that allows to find the index (position in the {@link ResultSet}) by * the {@link SimpleContent} instance that makes it necessary to fetch it. * * @param fetchContents * @return key: SimpleContent, value: Integer (position in ResultSet) */ protected Map<SimpleContent, Integer> buildResultPosMap( List<List<SimpleContent>> fetchContents ) { Map<SimpleContent, Integer> resultPosMap = new HashMap<SimpleContent, Integer>(); for ( int i = 0; i < fetchContents.size(); i++ ) { for ( SimpleContent content : fetchContents.get( i ) ) { resultPosMap.put( content, i ); } } return resultPosMap; } } /* ******************************************************************** Changes to this class. What the people have been up to: $Log: FeatureFetcher.java,v $ Revision 1.6 2006/11/29 16:59:54 mschneider Improved handling of native coordinate transformation. Revision 1.5 2006/11/27 09:07:53 poth JNI integration of proj4 has been removed. The CRS functionality now will be done by native deegree code. Revision 1.4 2006/11/09 17:40:11 mschneider Added hack for native coordinate system transformations. Needs review. Revision 1.3 2006/11/02 14:29:11 mschneider Javadoc / formatting fixes. Revision 1.2 2006/09/20 11:35:41 mschneider Merged datastore related messages with org.deegree.18n. Revision 1.1 2006/09/19 14:54:02 mschneider Cleaned up handling of VirtualContent, i.e. properties that are mapped to SQLFunctionCalls. ********************************************************************** */