0001: //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/io/datastore/sql/FeatureFetcher.java $
0002: /*---------------- FILE HEADER ------------------------------------------
0003:
0004: This file is part of deegree.
0005: Copyright (C) 2001-2008 by:
0006: Department of Geography, University of Bonn
0007: http://www.giub.uni-bonn.de/deegree/
0008: lat/lon GmbH
0009: http://www.lat-lon.de
0010:
0011: This library is free software; you can redistribute it and/or
0012: modify it under the terms of the GNU Lesser General Public
0013: License as published by the Free Software Foundation; either
0014: version 2.1 of the License, or (at your option) any later version.
0015:
0016: This library is distributed in the hope that it will be useful,
0017: but WITHOUT ANY WARRANTY; without even the implied warranty of
0018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0019: Lesser General Public License for more details.
0020:
0021: You should have received a copy of the GNU Lesser General Public
0022: License along with this library; if not, write to the Free Software
0023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0024:
0025: Contact:
0026:
0027: Andreas Poth
0028: lat/lon GmbH
0029: Aennchenstraße 19
0030: 53177 Bonn
0031: Germany
0032: E-Mail: poth@lat-lon.de
0033:
0034: Prof. Dr. Klaus Greve
0035: Department of Geography
0036: University of Bonn
0037: Meckenheimer Allee 166
0038: 53115 Bonn
0039: Germany
0040: E-Mail: greve@giub.uni-bonn.de
0041:
0042: ---------------------------------------------------------------------------*/
0043: package org.deegree.io.datastore.sql;
0044:
0045: import java.sql.Connection;
0046: import java.sql.PreparedStatement;
0047: import java.sql.ResultSet;
0048: import java.sql.SQLException;
0049: import java.util.ArrayList;
0050: import java.util.Collection;
0051: import java.util.HashMap;
0052: import java.util.HashSet;
0053: import java.util.List;
0054: import java.util.Map;
0055: import java.util.Set;
0056:
0057: import org.deegree.datatypes.QualifiedName;
0058: import org.deegree.framework.log.ILogger;
0059: import org.deegree.framework.log.LoggerFactory;
0060: import org.deegree.framework.util.StringTools;
0061: import org.deegree.i18n.Messages;
0062: import org.deegree.io.datastore.DatastoreException;
0063: import org.deegree.io.datastore.FeatureId;
0064: import org.deegree.io.datastore.PropertyPathResolver;
0065: import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
0066: import org.deegree.io.datastore.schema.MappedFeatureType;
0067: import org.deegree.io.datastore.schema.MappedGMLId;
0068: import org.deegree.io.datastore.schema.MappedGeometryPropertyType;
0069: import org.deegree.io.datastore.schema.MappedPropertyType;
0070: import org.deegree.io.datastore.schema.MappedSimplePropertyType;
0071: import org.deegree.io.datastore.schema.TableRelation;
0072: import org.deegree.io.datastore.schema.content.ConstantContent;
0073: import org.deegree.io.datastore.schema.content.MappingField;
0074: import org.deegree.io.datastore.schema.content.MappingGeometryField;
0075: import org.deegree.io.datastore.schema.content.SQLFunctionCall;
0076: import org.deegree.io.datastore.schema.content.SimpleContent;
0077: import org.deegree.model.crs.CRSFactory;
0078: import org.deegree.model.crs.CoordinateSystem;
0079: import org.deegree.model.crs.UnknownCRSException;
0080: import org.deegree.model.feature.Feature;
0081: import org.deegree.model.feature.FeatureFactory;
0082: import org.deegree.model.feature.FeatureProperty;
0083: import org.deegree.model.feature.schema.PropertyType;
0084: import org.deegree.model.spatialschema.Geometry;
0085: import org.deegree.ogcbase.PropertyPath;
0086: import org.deegree.ogcwebservices.wfs.operation.Query;
0087:
0088: /**
0089: * The only implementation of this abstract class is the {@link QueryHandler} class.
0090: * <p>
0091: * While the {@link QueryHandler} class performs the initial SELECT, {@link FeatureFetcher} is responsible for any
0092: * subsequent SELECTs that may be necessary.
0093: *
0094: * @see QueryHandler
0095: *
0096: * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
0097: * @author last edited by: $Author: apoth $
0098: *
0099: * @version $Revision: 9342 $, $Date: 2007-12-27 04:32:57 -0800 (Thu, 27 Dec 2007) $
0100: */
0101: abstract class FeatureFetcher extends AbstractRequestHandler {
0102:
0103: private static final ILogger LOG = LoggerFactory
0104: .getLogger(FeatureFetcher.class);
0105:
0106: // key: feature id of features that are generated or are in generation
0107: protected Set<FeatureId> featuresInGeneration = new HashSet<FeatureId>();
0108:
0109: // key: feature id value, value: Feature
0110: protected Map<FeatureId, Feature> featureMap = new HashMap<FeatureId, Feature>(
0111: 1000);
0112:
0113: // key: feature id value, value: property instances that contain the feature
0114: protected Map<FeatureId, List<FeatureProperty>> fidToPropertyMap = new HashMap<FeatureId, List<FeatureProperty>>();
0115:
0116: // provides virtual content (constants, sql functions, ...)
0117: protected VirtualContentProvider vcProvider;
0118:
0119: protected Query query;
0120:
0121: private CoordinateSystem queryCS;
0122:
0123: // key: geometry field, value: function call that transforms it to the queried CS
0124: private Map<MappingGeometryField, SQLFunctionCall> fieldToTransformCall = new HashMap<MappingGeometryField, SQLFunctionCall>();
0125:
0126: FeatureFetcher(AbstractSQLDatastore datastore,
0127: TableAliasGenerator aliasGenerator, Connection conn,
0128: Query query) throws DatastoreException {
0129: super (datastore, aliasGenerator, conn);
0130: this .query = query;
0131: if (this .query.getSrsName() != null) {
0132: try {
0133: this .queryCS = CRSFactory.create(this .query
0134: .getSrsName());
0135: } catch (UnknownCRSException e) {
0136: throw new DatastoreException(e.getMessage(), e);
0137: }
0138: }
0139: }
0140:
0141: /**
0142: * Builds a SELECT statement to fetch features / properties that are stored in a related table.
0143: *
0144: * @param fetchContents
0145: * table columns / functions to fetch
0146: * @param relations
0147: * table relations that lead to the table where the property is stored
0148: * @param resultValues
0149: * all retrieved columns from one result set row
0150: * @param resultPosMap
0151: * key class: SimpleContent, value class: Integer (this is the associated index in resultValues)
0152: * @return the statement or null if the keys in resultValues contain NULL values
0153: */
0154: private StatementBuffer buildSubsequentSelect(
0155: List<List<SimpleContent>> fetchContents,
0156: TableRelation[] relations, Object[] resultValues,
0157: Map<SimpleContent, Integer> resultPosMap) {
0158:
0159: this .aliasGenerator.reset();
0160: String[] tableAliases = this .aliasGenerator
0161: .generateUniqueAliases(relations.length);
0162:
0163: StatementBuffer query = new StatementBuffer();
0164: query.append("SELECT ");
0165: appendQualifiedContentList(query,
0166: tableAliases[tableAliases.length - 1], fetchContents);
0167: query.append(" FROM ");
0168: query.append(relations[0].getToTable());
0169: query.append(" ");
0170: query.append(tableAliases[0]);
0171:
0172: // append joins
0173: for (int i = 1; i < relations.length; i++) {
0174: query.append(" JOIN ");
0175: query.append(relations[i].getToTable());
0176: query.append(" ");
0177: query.append(tableAliases[i]);
0178: query.append(" ON ");
0179: MappingField[] fromFields = relations[i].getFromFields();
0180: MappingField[] toFields = relations[i].getToFields();
0181: for (int j = 0; j < fromFields.length; j++) {
0182: query.append(tableAliases[i - 1]);
0183: query.append('.');
0184: query.append(fromFields[j].getField());
0185: query.append('=');
0186: query.append(tableAliases[i]);
0187: query.append('.');
0188: query.append(toFields[j].getField());
0189: }
0190: }
0191:
0192: // append key constraints
0193: query.append(" WHERE ");
0194: MappingField[] fromFields = relations[0].getFromFields();
0195: MappingField[] toFields = relations[0].getToFields();
0196: for (int i = 0; i < fromFields.length; i++) {
0197: int resultPos = resultPosMap.get(fromFields[i]);
0198: Object keyValue = resultValues[resultPos];
0199: if (keyValue == null) {
0200: return null;
0201: }
0202: query.append(tableAliases[0]);
0203: query.append('.');
0204: query.append(toFields[i].getField());
0205: query.append("=?");
0206: query.addArgument(keyValue, toFields[i].getType());
0207: if (i != fromFields.length - 1) {
0208: query.append(" AND ");
0209: }
0210: }
0211: return query;
0212: }
0213:
0214: /**
0215: * Builds a SELECT statement to fetch the feature ids and the (concrete) feature types of feature properties that
0216: * are stored in a related table (currently limited to *one* join table).
0217: * <p>
0218: * This is only necessary for feature properties that contain feature types with more than one possible
0219: * substitution.
0220: *
0221: * @param relation1
0222: * first table relation that leads to the join table
0223: * @param relation2
0224: * second table relation that leads to the table where the property is stored
0225: * @param resultValues
0226: * all retrieved columns from one result set row
0227: * @param mappingFieldMap
0228: * key class: MappingField, value class: Integer (this is the associated index in resultValues)
0229: * @return the statement or null if the keys in resultValues contain NULL values
0230: */
0231: private StatementBuffer buildFeatureTypeSelect(
0232: TableRelation relation1, TableRelation relation2,
0233: Object[] resultValues, Map mappingFieldMap) {
0234: StatementBuffer query = new StatementBuffer();
0235: query.append("SELECT ");
0236: // append feature type column
0237: query.append(FT_COLUMN);
0238: // append feature id columns
0239: MappingField[] fidFields = relation2.getFromFields();
0240: for (int i = 0; i < fidFields.length; i++) {
0241: query.append(',');
0242: query.append(fidFields[i].getField());
0243: }
0244: query.append(" FROM ");
0245: query.append(relation1.getToTable());
0246: query.append(" WHERE ");
0247: // append key constraints
0248: MappingField[] fromFields = relation1.getFromFields();
0249: MappingField[] toFields = relation1.getToFields();
0250: for (int i = 0; i < fromFields.length; i++) {
0251: Integer resultPos = (Integer) mappingFieldMap
0252: .get(fromFields[i]);
0253: Object keyValue = resultValues[resultPos.intValue()];
0254: if (keyValue == null) {
0255: return null;
0256: }
0257: query.append(toFields[i].getField());
0258: query.append("=?");
0259: query.addArgument(keyValue, toFields[i].getType());
0260: if (i != fromFields.length - 1) {
0261: query.append(" AND ");
0262: }
0263: }
0264: return query;
0265: }
0266:
0267: /**
0268: * Builds a SELECT statement to fetch the feature id and the (concrete) feature type of a feature property that is
0269: * stored in a related table (with the fk in the current table).
0270: * <p>
0271: * This is only necessary for feature properties that contain feature types with more than one possible
0272: * substitution.
0273: *
0274: * TODO: Select the FT_ column beforehand.
0275: *
0276: * @param relation
0277: * table relation that leads to the subfeature table
0278: * @param resultValues
0279: * all retrieved columns from one result set row
0280: * @param mappingFieldMap
0281: * key class: MappingField, value class: Integer (this is the associated index in resultValues)
0282: * @return the statement or null if the keys in resultValues contain NULL values
0283: */
0284: private StatementBuffer buildFeatureTypeSelect(
0285: TableRelation relation, Object[] resultValues,
0286: Map mappingFieldMap) {
0287: StatementBuffer query = new StatementBuffer();
0288: query.append("SELECT DISTINCT ");
0289: // append feature type column
0290: query
0291: .append(FT_PREFIX
0292: + relation.getFromFields()[0].getField());
0293: // append feature id columns
0294: MappingField[] fidFields = relation.getFromFields();
0295: for (int i = 0; i < fidFields.length; i++) {
0296: query.append(',');
0297: query.append(fidFields[i].getField());
0298: }
0299: query.append(" FROM ");
0300: query.append(relation.getFromTable());
0301: query.append(" WHERE ");
0302: // append key constraints
0303: MappingField[] fromFields = relation.getFromFields();
0304: for (int i = 0; i < fromFields.length; i++) {
0305: Integer resultPos = (Integer) mappingFieldMap
0306: .get(fromFields[i]);
0307: Object keyValue = resultValues[resultPos.intValue()];
0308: if (keyValue == null) {
0309: return null;
0310: }
0311: query.append(fromFields[i].getField());
0312: query.append("=?");
0313: query.addArgument(keyValue, fromFields[i].getType());
0314: if (i != fromFields.length - 1) {
0315: query.append(" AND ");
0316: }
0317: }
0318: return query;
0319: }
0320:
0321: /**
0322: * Builds a SELECT statement that fetches one feature and it's properties.
0323: *
0324: * @param fid
0325: * id of the feature to fetch
0326: * @param table
0327: * root table of the feature
0328: * @param fetchContents
0329: * @return the statement or null if the keys in resultValues contain NULL values
0330: */
0331: private StatementBuffer buildFeatureSelect(FeatureId fid,
0332: String table, List<List<SimpleContent>> fetchContents) {
0333:
0334: StatementBuffer query = new StatementBuffer();
0335: query.append("SELECT ");
0336: appendQualifiedContentList(query, table, fetchContents);
0337: query.append(" FROM ");
0338: query.append(table);
0339: query.append(" WHERE ");
0340:
0341: // append feature id constraints
0342: MappingField[] fidFields = fid.getFidDefinition().getIdFields();
0343: for (int i = 0; i < fidFields.length; i++) {
0344: query.append(fidFields[i].getField());
0345: query.append("=?");
0346: query.addArgument(fid.getValue(i), fidFields[i].getType());
0347: if (i != fidFields.length - 1) {
0348: query.append(" AND ");
0349: }
0350: }
0351: return query;
0352: }
0353:
0354: /**
0355: * Extracts a feature from the values of a result set row.
0356: *
0357: * @param fid
0358: * feature id of the feature
0359: * @param requestedPropertyMap
0360: * requested <code>MappedPropertyType</code>s mapped to <code>Collection</code> of
0361: * <code>PropertyPath</code>s
0362: * @param resultPosMap
0363: * key class: MappingField, value class: Integer (this is the associated index in resultValues)
0364: * @param resultValues
0365: * all retrieved columns from one result set row
0366: * @return the extracted feature
0367: * @throws SQLException
0368: * if a JDBC related error occurs
0369: * @throws DatastoreException
0370: * @throws UnknownCRSException
0371: */
0372: protected Feature extractFeature(
0373: FeatureId fid,
0374: Map<MappedPropertyType, Collection<PropertyPath>> requestedPropertyMap,
0375: Map<SimpleContent, Integer> resultPosMap,
0376: Object[] resultValues) throws SQLException,
0377: DatastoreException, UnknownCRSException {
0378:
0379: LOG.logDebug("id = " + fid.getAsString());
0380:
0381: this .featuresInGeneration.add(fid);
0382:
0383: // extract the requested properties of the feature
0384: List<FeatureProperty> propertyList = new ArrayList<FeatureProperty>();
0385: for (MappedPropertyType requestedProperty : requestedPropertyMap
0386: .keySet()) {
0387: Collection<PropertyPath> propertyPaths = requestedPropertyMap
0388: .get(requestedProperty);
0389: // PropertyPath[] requestingPaths = PropertyPathResolver.determineSubPropertyPaths
0390: // (requestedProperty, propertyPaths);
0391: Collection<FeatureProperty> props = extractProperties(
0392: requestedProperty, propertyPaths, resultPosMap,
0393: resultValues);
0394: propertyList.addAll(props);
0395: }
0396: FeatureProperty[] properties = propertyList
0397: .toArray(new FeatureProperty[propertyList.size()]);
0398: Feature feature = FeatureFactory.createFeature(fid
0399: .getAsString(), fid.getFeatureType(), properties);
0400:
0401: this .featureMap.put(fid, feature);
0402: return feature;
0403: }
0404:
0405: /**
0406: * Extracts the feature id from the values of a result set row.
0407: *
0408: * @param ft
0409: * feature type for which the id shall be extracted
0410: * @param mfMap
0411: * key class: MappingField, value class: Integer (this is the associated index in resultValues)
0412: * @param resultValues
0413: * all retrieved columns from one result set row
0414: * @return the feature id
0415: * @throws DatastoreException
0416: */
0417: protected FeatureId extractFeatureId(MappedFeatureType ft,
0418: Map<SimpleContent, Integer> mfMap, Object[] resultValues)
0419: throws DatastoreException {
0420: MappingField[] idFields = ft.getGMLId().getIdFields();
0421: Object[] idValues = new Object[idFields.length];
0422: for (int i = 0; i < idFields.length; i++) {
0423: Integer resultPos = mfMap.get(idFields[i]);
0424: Object idValue = resultValues[resultPos.intValue()];
0425: if (idValue == null) {
0426: String msg = Messages.getMessage(
0427: "DATASTORE_FEATURE_ID_NULL", ft.getTable(), ft
0428: .getName(), idFields[i].getField());
0429: throw new DatastoreException(msg);
0430: }
0431: idValues[i] = idValue;
0432: }
0433: return new FeatureId(ft, idValues);
0434: }
0435:
0436: /**
0437: * Extracts the properties of the given property type from the values of a result set row. If the property is stored
0438: * in related table, only the key values are present in the result set row and more SELECTs are built and executed
0439: * to build the property.
0440: * <p>
0441: * NOTE: If the property is not stored in a related table, only one FeatureProperty is returned, otherwise the
0442: * number of properties depends on the multiplicity of the relation.
0443: *
0444: * @param pt
0445: * the mapped property type to be extracted
0446: * @param propertyPaths
0447: * property paths that refer to the property to be extracted
0448: * @param resultPosMap
0449: * key class: SimpleContent, value class: Integer (this is the associated index in resultValues)
0450: * @param resultValues
0451: * all retrieved columns from one result set row
0452: * @return Collection of FeatureProperty instances
0453: * @throws SQLException
0454: * if a JDBC related error occurs
0455: * @throws DatastoreException
0456: * @throws UnknownCRSException
0457: */
0458: private Collection<FeatureProperty> extractProperties(
0459: MappedPropertyType pt,
0460: Collection<PropertyPath> propertyPaths,
0461: Map<SimpleContent, Integer> resultPosMap,
0462: Object[] resultValues) throws SQLException,
0463: DatastoreException, UnknownCRSException {
0464:
0465: Collection<FeatureProperty> result = null;
0466:
0467: TableRelation[] tableRelations = pt.getTableRelations();
0468: if (tableRelations != null && tableRelations.length != 0) {
0469: LOG.logDebug("Fetching related properties: '"
0470: + pt.getName() + "'...");
0471: result = fetchRelatedProperties(pt.getName(), pt,
0472: propertyPaths, resultPosMap, resultValues);
0473: } else {
0474: Object propertyValue = null;
0475: if (pt instanceof MappedSimplePropertyType) {
0476: SimpleContent content = ((MappedSimplePropertyType) pt)
0477: .getContent();
0478: if (content instanceof MappingField) {
0479: Integer resultPos = resultPosMap.get(content);
0480: propertyValue = datastore.convertFromDBType(
0481: resultValues[resultPos.intValue()], pt
0482: .getType());
0483: } else if (content instanceof ConstantContent) {
0484: propertyValue = ((ConstantContent) content)
0485: .getValue();
0486: } else if (content instanceof SQLFunctionCall) {
0487: Integer resultPos = resultPosMap.get(content);
0488: propertyValue = resultValues[resultPos.intValue()];
0489: }
0490: } else if (pt instanceof MappedGeometryPropertyType) {
0491: MappingGeometryField field = ((MappedGeometryPropertyType) pt)
0492: .getMappingField();
0493: Integer resultPos = null;
0494: SQLFunctionCall transformCall = this .fieldToTransformCall
0495: .get(field);
0496: if (transformCall != null) {
0497: resultPos = resultPosMap.get(transformCall);
0498: } else {
0499: resultPos = resultPosMap.get(field);
0500: }
0501: propertyValue = resultValues[resultPos.intValue()];
0502:
0503: CoordinateSystem cs = ((MappedGeometryPropertyType) pt)
0504: .getCS();
0505: if (this .queryCS != null) {
0506: cs = this .queryCS;
0507: }
0508: propertyValue = this .datastore
0509: .convertDBToDeegreeGeometry(propertyValue, cs,
0510: conn);
0511: } else {
0512: String msg = "Unsupported property type: '"
0513: + pt.getClass().getName()
0514: + "' in QueryHandler.extractProperties(). ";
0515: LOG.logError(msg);
0516: throw new IllegalArgumentException(msg);
0517: }
0518: FeatureProperty property = FeatureFactory
0519: .createFeatureProperty(pt.getName(), propertyValue);
0520: result = new ArrayList<FeatureProperty>();
0521: result.add(property);
0522: }
0523: return result;
0524: }
0525:
0526: /**
0527: * Extracts a {@link FeatureId} from one result set row.
0528: *
0529: * @param ft
0530: * @param rs
0531: * @param startIndex
0532: * @return feature id from result set row
0533: * @throws SQLException
0534: */
0535: private FeatureId extractFeatureId(MappedFeatureType ft,
0536: ResultSet rs, int startIndex) throws SQLException {
0537: MappedGMLId gmlId = ft.getGMLId();
0538: MappingField[] idFields = gmlId.getIdFields();
0539:
0540: Object[] idValues = new Object[idFields.length];
0541: for (int i = 0; i < idValues.length; i++) {
0542: idValues[i] = rs.getObject(i + startIndex);
0543: }
0544: return new FeatureId(ft, idValues);
0545: }
0546:
0547: /**
0548: * Determines the columns / functions that have to be fetched from the table of the given {@link MappedFeatureType}
0549: * and associates identical columns / functions to avoid that the same column / function is SELECTed more than once.
0550: * <p>
0551: * Identical columns are put into the same (inner) list.
0552: * <p>
0553: * The following {@link SimpleContent} instances of the {@link MappedFeatureType}s annotation are used to build the
0554: * list:
0555: * <ul>
0556: * <li>MappingFields from the wfs:gmlId - annotation element of the feature type definition</li>
0557: * <li>MappingFields in the annotations of the property element definitions; if the property's content is stored in
0558: * a related table, the MappingFields used in the first wfs:Relation element's wfs:From element are considered</li>
0559: * <li>SQLFunctionCalls in the annotations of the property element definitions; if the property's (derived) content
0560: * is stored in a related table, the MappingFields used in the first wfs:Relation element's wfs:From element are
0561: * considered</li>
0562: * </ul>
0563: *
0564: * @param ft
0565: * feature type for which the content list is built
0566: * @param requestedProps
0567: * requested properties
0568: * @return List of Lists (that contains SimpleContent instance that refer the same column)
0569: * @throws DatastoreException
0570: */
0571: protected List<List<SimpleContent>> determineFetchContents(
0572: MappedFeatureType ft, PropertyType[] requestedProps)
0573: throws DatastoreException {
0574:
0575: List<List<SimpleContent>> fetchList = new ArrayList<List<SimpleContent>>();
0576:
0577: // helper lookup map (column names -> referencing MappingField instances)
0578: Map<String, List<SimpleContent>> columnsMap = new HashMap<String, List<SimpleContent>>();
0579:
0580: // add table columns that are necessary to build the feature's gml id
0581: MappingField[] idFields = ft.getGMLId().getIdFields();
0582: for (int i = 0; i < idFields.length; i++) {
0583: List<SimpleContent> mappingFieldList = columnsMap
0584: .get(idFields[i].getField());
0585: if (mappingFieldList == null) {
0586: mappingFieldList = new ArrayList<SimpleContent>();
0587: }
0588: mappingFieldList.add(idFields[i]);
0589: columnsMap.put(idFields[i].getField(), mappingFieldList);
0590: }
0591:
0592: // add columns that are necessary to build the requested feature properties
0593: for (int i = 0; i < requestedProps.length; i++) {
0594: MappedPropertyType pt = (MappedPropertyType) requestedProps[i];
0595: TableRelation[] tableRelations = pt.getTableRelations();
0596: if (tableRelations != null && tableRelations.length != 0) {
0597: // if property is not stored in feature type's table, retrieve key fields of
0598: // the first relation's 'From' element
0599: MappingField[] fields = tableRelations[0]
0600: .getFromFields();
0601: for (int k = 0; k < fields.length; k++) {
0602: List<SimpleContent> list = columnsMap.get(fields[k]
0603: .getField());
0604: if (list == null) {
0605: list = new ArrayList<SimpleContent>();
0606: }
0607: list.add(fields[k]);
0608: columnsMap.put(fields[k].getField(), list);
0609: }
0610: // if (content instanceof FeaturePropertyContent) {
0611: // if (tableRelations.length == 1) {
0612: // // if feature property contains an abstract feature type, retrieve
0613: // // feature type as well (stored in column named "FT_fk")
0614: // MappedFeatureType subFeatureType = ( (FeaturePropertyContent) content )
0615: // .getFeatureTypeReference().getFeatureType();
0616: // if (subFeatureType.isAbstract()) {
0617: // String typeColumn = FT_PREFIX + fields [0].getField();
0618: // columnsMap.put (typeColumn, new ArrayList ());
0619: // }
0620: // }
0621: // }
0622: } else {
0623: String column = null;
0624: SimpleContent content = null;
0625: if (pt instanceof MappedSimplePropertyType) {
0626: content = ((MappedSimplePropertyType) pt)
0627: .getContent();
0628: if (content instanceof MappingField) {
0629: column = ((MappingField) content).getField();
0630: } else {
0631: // ignore virtual properties here (handled below)
0632: continue;
0633: }
0634: } else if (pt instanceof MappedGeometryPropertyType) {
0635: content = determineFetchContent((MappedGeometryPropertyType) pt);
0636: column = ((MappedGeometryPropertyType) pt)
0637: .getMappingField().getField();
0638: } else {
0639: assert false;
0640: }
0641: List<SimpleContent> contentList = columnsMap
0642: .get(column);
0643: if (contentList == null) {
0644: contentList = new ArrayList<SimpleContent>();
0645: }
0646: contentList.add(content);
0647: columnsMap.put(column, contentList);
0648: }
0649: }
0650:
0651: fetchList.addAll(columnsMap.values());
0652:
0653: // add functions that are necessary to build the requested feature properties
0654: for (int i = 0; i < requestedProps.length; i++) {
0655: MappedPropertyType pt = (MappedPropertyType) requestedProps[i];
0656: TableRelation[] tableRelations = pt.getTableRelations();
0657: if (tableRelations == null || tableRelations.length == 0) {
0658: if (pt instanceof MappedSimplePropertyType) {
0659: SimpleContent content = ((MappedSimplePropertyType) pt)
0660: .getContent();
0661: if (content instanceof SQLFunctionCall) {
0662: List<SimpleContent> functionCallList = new ArrayList<SimpleContent>(
0663: 1);
0664: functionCallList.add(content);
0665: fetchList.add(functionCallList);
0666: } else {
0667: // ignore other content types here
0668: continue;
0669: }
0670: }
0671: }
0672: }
0673: return fetchList;
0674: }
0675:
0676: /**
0677: * Determines a {@link SimpleContent} object that represents the queried {@link MappedGeometryProperty} in the
0678: * requested SRS.
0679: * <p>
0680: * <ul>
0681: * <li>If the query SRS is identical to the geometry field's SRS (and thus the SRS of the stored geometry, the
0682: * corresponding {@link MappingGeometryField} is returned.</li>
0683: * <li>If the query SRS differs from the geometry field's SRS (and thus the SRS of the stored geometry, an
0684: * {@link SQLFunctionCall} is returned that refers to the stored geometry, but transforms it to the queried SRS.</li>
0685: * </ul>
0686: *
0687: * @param pt
0688: * geometry property
0689: * @return a <code>SimpleContent</code> instance that represents the queried geometry property
0690: * @throws DatastoreException
0691: * if the transform call cannot be generated
0692: */
0693: private SimpleContent determineFetchContent(
0694: MappedGeometryPropertyType pt) throws DatastoreException {
0695:
0696: MappingGeometryField field = pt.getMappingField();
0697: SimpleContent content = field;
0698:
0699: String queriedSRS = this .datastore.checkTransformation(pt,
0700: this .query.getSrsName());
0701: if (queriedSRS != null) {
0702: content = this .fieldToTransformCall.get(field);
0703: if (content == null) {
0704: content = this .datastore.buildSRSTransformCall(pt,
0705: queriedSRS);
0706: this .fieldToTransformCall.put(field,
0707: (SQLFunctionCall) content);
0708: }
0709: }
0710: return content;
0711: }
0712:
0713: /**
0714: * Retrieves the feature with the given feature id.
0715: *
0716: * @param fid
0717: * @param requestedPaths
0718: * @return the feature with the given type and feature id, may be null
0719: * @throws SQLException
0720: * @throws DatastoreException
0721: * @throws UnknownCRSException
0722: */
0723: private Feature fetchFeature(FeatureId fid,
0724: PropertyPath[] requestedPaths) throws SQLException,
0725: DatastoreException, UnknownCRSException {
0726:
0727: Feature feature = null;
0728: MappedFeatureType ft = fid.getFeatureType();
0729: // TODO what about aliases here?
0730: Map<MappedPropertyType, Collection<PropertyPath>> requestedPropMap = PropertyPathResolver
0731: .determineFetchProperties(ft, null, requestedPaths);
0732: MappedPropertyType[] requestedProps = requestedPropMap
0733: .keySet()
0734: .toArray(
0735: new MappedPropertyType[requestedPropMap.size()]);
0736:
0737: if (requestedProps.length > 0) {
0738:
0739: // determine contents (fields / functions) that must be SELECTed from root table
0740: List<List<SimpleContent>> fetchContents = determineFetchContents(
0741: ft, requestedProps);
0742: Map<SimpleContent, Integer> resultPosMap = buildResultPosMap(fetchContents);
0743:
0744: // build feature query
0745: StatementBuffer query = buildFeatureSelect(fid, ft
0746: .getTable(), fetchContents);
0747: LOG.logDebug("Feature query: '" + query + "'");
0748: Object[] resultValues = new Object[fetchContents.size()];
0749: PreparedStatement stmt = null;
0750: ResultSet rs = null;
0751: try {
0752: stmt = this .datastore
0753: .prepareStatement(this .conn, query);
0754: rs = stmt.executeQuery();
0755:
0756: if (rs.next()) {
0757: // collect result values
0758: for (int i = 0; i < resultValues.length; i++) {
0759: resultValues[i] = rs.getObject(i + 1);
0760: }
0761: feature = extractFeature(fid, requestedPropMap,
0762: resultPosMap, resultValues);
0763: } else {
0764: String msg = Messages.getMessage(
0765: "DATASTORE_FEATURE_QUERY_NO_RESULT", query
0766: .getQueryString());
0767: LOG.logError(msg);
0768: throw new DatastoreException(msg);
0769: }
0770: if (rs.next()) {
0771: String msg = Messages
0772: .getMessage(
0773: "DATASTORE_FEATURE_QUERY_MORE_THAN_ONE_RESULT",
0774: query.getQueryString());
0775: LOG.logError(msg);
0776: throw new DatastoreException(msg);
0777: }
0778: } finally {
0779: try {
0780: if (rs != null) {
0781: rs.close();
0782: }
0783: } finally {
0784: if (stmt != null) {
0785: stmt.close();
0786: }
0787: }
0788: }
0789: }
0790: return feature;
0791: }
0792:
0793: /**
0794: *
0795: * @param propertyName
0796: * @param pt
0797: * @param propertyPaths
0798: * property paths that refer to the property to be extracted
0799: * @param resultPosMap
0800: * key class: MappingField, value class: Integer (this is the associated index in resultValues)
0801: * @param resultValues
0802: * all retrieved columns from one result set row
0803: * @return Collection of FeatureProperty instances
0804: * @throws SQLException
0805: * if a JDBC related error occurs
0806: * @throws DatastoreException
0807: * @throws UnknownCRSException
0808: */
0809: private Collection<FeatureProperty> fetchRelatedProperties(
0810: QualifiedName propertyName, MappedPropertyType pt,
0811: Collection<PropertyPath> propertyPaths,
0812: Map<SimpleContent, Integer> resultPosMap,
0813: Object[] resultValues) throws SQLException,
0814: DatastoreException, UnknownCRSException {
0815:
0816: Collection<FeatureProperty> result = new ArrayList<FeatureProperty>(
0817: 100);
0818: PreparedStatement stmt = null;
0819: ResultSet rs = null;
0820: try {
0821: if (pt instanceof MappedSimplePropertyType) {
0822: SimpleContent content = ((MappedSimplePropertyType) pt)
0823: .getContent();
0824:
0825: // TODO check for invalid content types
0826: List<SimpleContent> fetchContents = new ArrayList<SimpleContent>(
0827: 1);
0828: List<List<SimpleContent>> fetchContentsList = new ArrayList<List<SimpleContent>>(
0829: 1);
0830: fetchContents.add(content);
0831: fetchContentsList.add(fetchContents);
0832:
0833: StatementBuffer query = buildSubsequentSelect(
0834: fetchContentsList, pt.getTableRelations(),
0835: resultValues, resultPosMap);
0836: LOG.logDebug("Subsequent query: '" + query + "'");
0837: if (query != null) {
0838: stmt = this .datastore.prepareStatement(this .conn,
0839: query);
0840: rs = stmt.executeQuery();
0841: while (rs.next()) {
0842: Object propertyValue = datastore
0843: .convertFromDBType(rs.getObject(1), pt
0844: .getType());
0845: FeatureProperty property = FeatureFactory
0846: .createFeatureProperty(propertyName,
0847: propertyValue);
0848: result.add(property);
0849: }
0850: }
0851:
0852: } else if (pt instanceof MappedGeometryPropertyType) {
0853: SimpleContent content = ((MappedGeometryPropertyType) pt)
0854: .getMappingField();
0855: CoordinateSystem cs = ((MappedGeometryPropertyType) pt)
0856: .getCS();
0857:
0858: if (this .queryCS != null) {
0859: cs = this .queryCS;
0860: }
0861: content = determineFetchContent((MappedGeometryPropertyType) pt);
0862:
0863: List<SimpleContent> fetchContents = new ArrayList<SimpleContent>(
0864: 1);
0865: List<List<SimpleContent>> fetchContentsList = new ArrayList<List<SimpleContent>>(
0866: 1);
0867: fetchContents.add(content);
0868: fetchContentsList.add(fetchContents);
0869:
0870: StatementBuffer query = buildSubsequentSelect(
0871: fetchContentsList, pt.getTableRelations(),
0872: resultValues, resultPosMap);
0873: LOG.logDebug("Subsequent query: '" + query + "'");
0874: if (query != null) {
0875: stmt = this .datastore.prepareStatement(this .conn,
0876: query);
0877: rs = stmt.executeQuery();
0878: while (rs.next()) {
0879: Object value = rs.getObject(1);
0880: Geometry geometry = this .datastore
0881: .convertDBToDeegreeGeometry(value, cs,
0882: this .conn);
0883: FeatureProperty property = FeatureFactory
0884: .createFeatureProperty(propertyName,
0885: geometry);
0886: result.add(property);
0887: }
0888: }
0889: } else if (pt instanceof MappedFeaturePropertyType) {
0890: MappedFeatureType ft = ((MappedFeaturePropertyType) pt)
0891: .getFeatureTypeReference().getFeatureType();
0892: MappedFeatureType[] substitutions = ft
0893: .getConcreteSubstitutions();
0894: if (substitutions.length > 1) {
0895: // if feature type has more than one concrete substitution, determine concrete
0896: // feature type first
0897: String msg = StringTools.concat(200,
0898: "FeatureType '", ft.getName(),
0899: "' has more than one concrete ",
0900: "substitution. Need to determine ",
0901: "feature type table first.");
0902: LOG.logDebug(msg);
0903: LOG.logDebug("Property: " + pt.getName());
0904: TableRelation[] tableRelations = pt
0905: .getTableRelations();
0906: if (tableRelations.length == 2) {
0907: StatementBuffer query = buildFeatureTypeSelect(
0908: tableRelations[0], tableRelations[1],
0909: resultValues, resultPosMap);
0910: LOG.logDebug("Feature type (and id) query: '"
0911: + query + "'");
0912: if (query != null) {
0913: stmt = this .datastore.prepareStatement(
0914: this .conn, query);
0915: rs = stmt.executeQuery();
0916: while (rs.next()) {
0917: String featureTypeName = rs
0918: .getString(1);
0919: MappedFeatureType concreteFeatureType = ft
0920: .getGMLSchema().getFeatureType(
0921: featureTypeName);
0922: if (concreteFeatureType == null) {
0923: msg = StringTools
0924: .concat(
0925: 200,
0926: "Lookup of concrete feature type '",
0927: featureTypeName,
0928: "' failed: ",
0929: " Inconsistent featuretype column!?");
0930: LOG.logError(msg);
0931: throw new DatastoreException(msg);
0932: }
0933: FeatureId fid = extractFeatureId(
0934: concreteFeatureType, rs, 2);
0935: msg = StringTools
0936: .concat(
0937: 200,
0938: "Subfeature '",
0939: fid.getAsString(),
0940: "' has concrete feature type '",
0941: concreteFeatureType
0942: .getName(),
0943: "'.");
0944: LOG.logDebug(msg);
0945:
0946: if (!this .featuresInGeneration
0947: .contains(fid)) {
0948: PropertyPath[] subPropertyPaths = PropertyPathResolver
0949: .determineSubPropertyPaths(
0950: concreteFeatureType,
0951: propertyPaths);
0952: Feature feature = fetchFeature(fid,
0953: subPropertyPaths);
0954: if (feature != null) {
0955: FeatureProperty property = FeatureFactory
0956: .createFeatureProperty(
0957: propertyName,
0958: feature);
0959: result.add(property);
0960: }
0961: } else {
0962: FeatureProperty property = FeatureFactory
0963: .createFeatureProperty(
0964: propertyName, null);
0965: addToFidToPropertyMap(fid, property);
0966: result.add(property);
0967: }
0968: }
0969: }
0970: } else if (tableRelations.length == 1) {
0971: StatementBuffer query = buildFeatureTypeSelect(
0972: tableRelations[0], resultValues,
0973: resultPosMap);
0974: LOG.logDebug("Feature type (and id) query: '"
0975: + query + "'");
0976: if (query != null) {
0977: stmt = this .datastore.prepareStatement(
0978: this .conn, query);
0979: rs = stmt.executeQuery();
0980: while (rs.next()) {
0981: String featureTypeName = rs
0982: .getString(1);
0983: MappedFeatureType concreteFeatureType = ft
0984: .getGMLSchema().getFeatureType(
0985: featureTypeName);
0986: if (concreteFeatureType == null) {
0987: msg = StringTools
0988: .concat(
0989: 200,
0990: "Lookup of concrete feature type '",
0991: featureTypeName,
0992: "' failed: ",
0993: " Inconsistent featuretype column!?");
0994: LOG.logError(msg);
0995: throw new DatastoreException(msg);
0996: }
0997:
0998: FeatureId fid = extractFeatureId(
0999: concreteFeatureType, rs, 2);
1000:
1001: msg = StringTools
1002: .concat(
1003: 200,
1004: "Subfeature '",
1005: fid.getAsString(),
1006: "' has concrete feature type '",
1007: concreteFeatureType
1008: .getName(),
1009: "'.");
1010: LOG.logDebug(msg);
1011:
1012: FeatureProperty property = null;
1013: if (!this .featuresInGeneration
1014: .contains(fid)) {
1015: PropertyPath[] subPropertyPaths = PropertyPathResolver
1016: .determineSubPropertyPaths(
1017: concreteFeatureType,
1018: propertyPaths);
1019: Feature feature = fetchFeature(fid,
1020: subPropertyPaths);
1021: if (feature != null) {
1022: property = FeatureFactory
1023: .createFeatureProperty(
1024: propertyName,
1025: feature);
1026: result.add(property);
1027: }
1028:
1029: } else {
1030: property = FeatureFactory
1031: .createFeatureProperty(
1032: propertyName, null);
1033: addToFidToPropertyMap(fid, property);
1034: result.add(property);
1035: }
1036: }
1037: }
1038: } else {
1039: msg = StringTools
1040: .concat(
1041: 200,
1042: "Querying of feature properties ",
1043: "with a content type with more than one ",
1044: "concrete substitution is not implemented for ",
1045: tableRelations.length,
1046: " TableRelations.");
1047: throw new DatastoreException(msg);
1048: }
1049: } else {
1050: // feature type is the only substitutable concrete feature type
1051: PropertyPath[] subPropertyPaths = PropertyPathResolver
1052: .determineSubPropertyPaths(ft,
1053: propertyPaths);
1054: // TODO aliases?
1055: Map<MappedPropertyType, Collection<PropertyPath>> requestedPropertiesMap = PropertyPathResolver
1056: .determineFetchProperties(ft, null,
1057: subPropertyPaths);
1058: MappedPropertyType[] requestedProperties = requestedPropertiesMap
1059: .keySet()
1060: .toArray(
1061: new MappedPropertyType[requestedPropertiesMap
1062: .size()]);
1063:
1064: // determine contents (fields / functions) that needs to be SELECTed from
1065: // current table
1066: List<List<SimpleContent>> fetchContents = determineFetchContents(
1067: ft, requestedProperties);
1068: Map<SimpleContent, Integer> newResultPosMap = buildResultPosMap(fetchContents);
1069:
1070: StatementBuffer query = buildSubsequentSelect(
1071: fetchContents, pt.getTableRelations(),
1072: resultValues, resultPosMap);
1073: LOG.logDebug("Subsequent query: '" + query + "'");
1074:
1075: if (query != null) {
1076: Object[] newResultValues = new Object[fetchContents
1077: .size()];
1078: stmt = this .datastore.prepareStatement(
1079: this .conn, query);
1080: rs = stmt.executeQuery();
1081: while (rs.next()) {
1082: // cache result values
1083: for (int i = 0; i < newResultValues.length; i++) {
1084: newResultValues[i] = rs
1085: .getObject(i + 1);
1086: }
1087: FeatureId fid = extractFeatureId(ft,
1088: newResultPosMap, newResultValues);
1089: FeatureProperty property = null;
1090: if (!this .featuresInGeneration
1091: .contains(fid)) {
1092: Feature feature = extractFeature(fid,
1093: requestedPropertiesMap,
1094: newResultPosMap,
1095: newResultValues);
1096: property = FeatureFactory
1097: .createFeatureProperty(
1098: propertyName, feature);
1099: } else {
1100: property = FeatureFactory
1101: .createFeatureProperty(
1102: propertyName, null);
1103: addToFidToPropertyMap(fid, property);
1104: }
1105: result.add(property);
1106: }
1107: }
1108: }
1109: } else {
1110: String msg = "Unsupported content type: '"
1111: + pt.getClass().getName()
1112: + "' in QueryHandler.fetchRelatedProperties().";
1113: throw new IllegalArgumentException(msg);
1114: }
1115: } finally {
1116: try {
1117: if (rs != null) {
1118: rs.close();
1119: }
1120: if (stmt != null) {
1121: stmt.close();
1122: }
1123: } finally {
1124: if (stmt != null) {
1125: stmt.close();
1126: }
1127: }
1128: }
1129: return result;
1130: }
1131:
1132: private void addToFidToPropertyMap(FeatureId fid,
1133: FeatureProperty property) {
1134: List<FeatureProperty> properties = this .fidToPropertyMap
1135: .get(fid);
1136: if (properties == null) {
1137: properties = new ArrayList<FeatureProperty>();
1138: this .fidToPropertyMap.put(fid, properties);
1139: }
1140: properties.add(property);
1141: }
1142:
1143: protected void appendQualifiedContentList(StatementBuffer query,
1144: String tableAlias, List<List<SimpleContent>> fetchContents) {
1145:
1146: for (int i = 0; i < fetchContents.size(); i++) {
1147: SimpleContent content = fetchContents.get(i).get(0);
1148: if (content instanceof MappingField) {
1149: appendQualifiedColumn(query, tableAlias,
1150: ((MappingField) content).getField());
1151: } else if (content instanceof SQLFunctionCall) {
1152: this .vcProvider.appendSQLFunctionCall(query,
1153: tableAlias, (SQLFunctionCall) content);
1154: } else {
1155: assert false;
1156: }
1157: if (i != fetchContents.size() - 1) {
1158: query.append(",");
1159: }
1160: }
1161: }
1162:
1163: /**
1164: * Builds a lookup map that allows to find the index (position in the {@link ResultSet}) by the
1165: * {@link SimpleContent} instance that makes it necessary to fetch it.
1166: *
1167: * @param fetchContents
1168: * @return key: SimpleContent, value: Integer (position in ResultSet)
1169: */
1170: protected Map<SimpleContent, Integer> buildResultPosMap(
1171: List<List<SimpleContent>> fetchContents) {
1172:
1173: Map<SimpleContent, Integer> resultPosMap = new HashMap<SimpleContent, Integer>();
1174: for (int i = 0; i < fetchContents.size(); i++) {
1175: for (SimpleContent content : fetchContents.get(i)) {
1176: resultPosMap.put(content, i);
1177: }
1178: }
1179: return resultPosMap;
1180: }
1181: }
|