//$Header: /home/deegree/jail/deegreerepository/deegree/src/org/deegree/io/datastore/sde/AbstractSDERequestHandler.java,v 1.6 2006/08/23 16:34:06 mschneider Exp $ /*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2006 by: M.O.S.S. Computer Grafik Systeme GmbH Hohenbrunner Weg 13 D-82024 Taufkirchen http://www.moss.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 ---------------------------------------------------------------------------*/ package org.deegree.io.datastore.sde; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.io.datastore.DatastoreException; import org.deegree.io.datastore.FeatureId; 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.MappingField; import org.deegree.io.datastore.schema.content.SimpleContent; import org.deegree.io.datastore.sql.TableAliasGenerator; import org.deegree.io.sdeapi.SDEConnection; import org.deegree.model.feature.schema.PropertyType; import org.deegree.model.filterencoding.ComplexFilter; import org.deegree.model.filterencoding.Filter; import com.esri.sde.sdk.client.SeFilter; import com.esri.sde.sdk.client.SeQuery; import com.esri.sde.sdk.client.SeRow; import com.esri.sde.sdk.client.SeSqlConstruct; /** * Handles <code>Transaction</code> requests to SQL based datastores. * * @author <a href="mailto:cpollmann@moss.de">Christoph Pollmann</a> * * @author last edited by: $Author: mschneider $ * * @version 2.0, $Revision: 1.6 $ * * @since 2.0 */ public class AbstractSDERequestHandler { private static final ILogger LOG = LoggerFactory.getLogger( AbstractSDERequestHandler.class ); protected static final String FT_COLUMN = "featuretype"; protected static final String FT_PREFIX = "ft_"; protected SDEDatastore datastore; protected TableAliasGenerator aliasGenerator; protected SDEConnection conn; /** * Creates a new instance of <code>AbstractSDERequestHandler</code> from the given parameters. * * @param datastore * @param aliasGenerator * @param conn * @throws DatastoreException */ public AbstractSDERequestHandler( SDEDatastore datastore, TableAliasGenerator aliasGenerator, SDEConnection conn ) { this.datastore = datastore; this.aliasGenerator = aliasGenerator; this.conn = conn; } /** * Returns the underlying <code>AbstractSQLDatastore</code>. * * @return the underlying <code>AbstractSQLDatastore</code>. */ public SDEDatastore getDatastore() { return this.datastore; } /** * Returns the underlying <code>AbstractSQLDatastore</code>. * * @return the underlying <code>AbstractSQLDatastore</code>. */ public SDEConnection getConnection() { return this.conn; } /** * Returns the underlying <code>AbstractSQLDatastore</code>. * * @return the underlying <code>AbstractSQLDatastore</code>. */ public TableAliasGenerator getAliasGenerator() { return this.aliasGenerator; } /** * Determines the feature ids that are matched by the given filter. * * @param filter * @return the feature ids that are matched by the given filter. * @throws DatastoreException */ public FeatureId[] determineAffectedFIDs( MappedFeatureType mappedFeatureType, Filter filter ) throws DatastoreException { SDEWhereBuilder whereBuilder = this.datastore.getWhereBuilder( mappedFeatureType, filter, new TableAliasGenerator() ); // if no filter is given FeatureId[] fids = null; SeQuery stmt = null; try { stmt = buildInitialFIDSelect( mappedFeatureType, whereBuilder ); stmt.execute(); fids = extractFeatureIds( stmt, mappedFeatureType ); } catch ( Exception e ) { throw new DatastoreException( "Error performing the delete transaction operation on mapped feature type '" + mappedFeatureType.getName() + "'." ); } finally { try { if ( stmt != null ) { stmt.close(); } } catch ( Exception e ) { } } return fids; } /** * Builds the initial SELECT statement that retrieves the feature ids that are matched by the * given <code>WhereBuilder</code>. * <p> * The statement is structured like this: * <ul> * <li><code>SELECT</code></li> * <li>comma-separated list of qualified fid fields</li> * <li><code>FROM</code></li> * <li>comma-separated list of tables and their aliases (this is needed to constrain the paths * to selected XPath-PropertyNames)</li> * <li><code>WHERE</code></li> * <li>SQL representation of the Filter expression</li> * </ul> * * @param rootFeatureType * @param whereBuilder * @return * @throws DatastoreException */ protected SeQuery buildInitialFIDSelect( MappedFeatureType rootFeatureType, SDEWhereBuilder whereBuilder ) { SeQuery query = null; try { StringBuffer whereCondition = new StringBuffer(); whereBuilder.appendWhereCondition( whereCondition ); SeSqlConstruct constr = new SeSqlConstruct( rootFeatureType.getTable(), whereCondition.toString() ); String[] fidColumns = getFeatureIdColumns( rootFeatureType ); query = new SeQuery( getConnection().getConnection(), fidColumns, constr ); if ( whereBuilder.getFilter() instanceof ComplexFilter ) { // There is NO chance, to make a new SeCoordinateReference equal to the existing crs of the requested layer. // So, we give it a chance, by passing the layer definitions (and its associated crs) to the whereBuilder method List layers = getConnection().getConnection().getLayers(); SeFilter[] spatialFilter = whereBuilder.buildSpatialFilter( (ComplexFilter) whereBuilder.getFilter(), layers ); if ( null != spatialFilter && 0 < spatialFilter.length ) { query.setSpatialConstraints( SeQuery.SE_OPTIMIZE, false, spatialFilter ); } } query.prepareQuery(); } catch ( Exception e ) { e.printStackTrace(); } return query; } /** * Appends the alias qualified columns that make up the feature id to the given query. * * @param featureType * @param tableAlias * @param query */ protected String[] getFeatureIdColumns( MappedFeatureType featureType ) { MappingField[] fidFields = featureType.getGMLId().getIdFields(); String[] columns = new String[fidFields.length]; for ( int i = 0; i < fidFields.length; i++ ) { columns[i] = fidFields[i].getField(); } return columns; } public FeatureId[] extractFeatureIds( SeQuery stmt, MappedFeatureType featureType ) throws Exception { List<FeatureId> featureIdList = new ArrayList<FeatureId>(); MappedGMLId gmlId = featureType.getGMLId(); MappingField[] idFields = gmlId.getIdFields(); SeRow row = null; for ( ;; ) { try { row = stmt.fetch(); } catch ( Exception e ) { row = null; } if ( null == row ) break; Object[] idValues = new Object[idFields.length]; for ( int i = 0; i < idValues.length; i++ ) { idValues[i] = row.getObject( i ); } featureIdList.add( new FeatureId( gmlId, idValues ) ); } return featureIdList.toArray( new FeatureId[featureIdList.size()] ); } /** * Builds a helper map that contains the column names of the feature type's table as keys. Each * column name is mapped to a <code>List</code> containing the <code>MappingField</code> * instances that refer to this column. * <p> * The following MappingField instances of the feature type's annotation are used to build the * map: * <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> * </ul> * * @param featureType feature type for which the map is built * @param requestedProperties requested properties * @return key class: String (column names), value class: List (containing MappingField * instances) */ protected Map<String, List<MappingField>> buildColumnsMap( MappedFeatureType featureType, PropertyType[] requestedProperties, boolean withIdFields ) { Map<String, List<MappingField>> columnsMap = new HashMap<String, List<MappingField>>(); // add table columns that are necessary to build the feature's gml id if ( withIdFields ) { MappingField[] idFields = featureType.getGMLId().getIdFields(); for ( int i = 0; i < idFields.length; i++ ) { List<MappingField> mappingFieldList = columnsMap.get( idFields[i].getField() ); if ( mappingFieldList == null ) { mappingFieldList = new ArrayList<MappingField>(); } 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 < requestedProperties.length; i++ ) { MappedPropertyType pt = (MappedPropertyType) requestedProperties [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 j = 0; j < fields.length; j++ ) { List list = columnsMap.get( fields[j].getField() ); if ( list == null ) { list = new ArrayList(); } list.add( fields[j] ); columnsMap.put( fields[j].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 { MappingField field = null; if ( pt instanceof MappedSimplePropertyType ) { SimpleContent content = (SimpleContent) pt; if (content instanceof MappingField) { field = (MappingField) content; } else { // ignore virtual properties here continue; } } else if ( pt instanceof MappedGeometryPropertyType ) { field = ( (MappedGeometryPropertyType) pt ).getMappingField(); } else { String msg = "Unsupported property type: '" + pt.getClass().getName() + "' in QueryHandler.buildColumnsMap(). "; LOG.logError( msg ); throw new IllegalArgumentException( msg ); } List list = columnsMap.get( field.getField() ); if ( list == null ) { list = new ArrayList(); } list.add( field ); columnsMap.put( field.getField(), list ); } } return columnsMap; } /** * Builds a lookup map that contains <code>MappingField</code> instances as keys. Each * <code>MappingField</code> is mapped to the index position (an <code>Integer</code>) of * the MappingField's field name in the given column array. * * @param columns * @param columnsMap * @return key class: MappingField, value class: Integer (index of field name in columns) */ protected Map buildMappingFieldMap( String[] columns, Map columnsMap ) { Map mappingFieldMap = new HashMap(); for ( int i = 0; i < columns.length; i++ ) { Integer resultPos = new Integer( i ); List mappingFieldList = (List) columnsMap.get( columns[i] ); Iterator iter = mappingFieldList.iterator(); while ( iter.hasNext() ) { mappingFieldMap.put( iter.next(), resultPos ); } } return mappingFieldMap; } } /*************************************************************************************************** * Changes to this class. What the people have been up to: * $Log: AbstractSDERequestHandler.java,v $ * Revision 1.6 2006/08/23 16:34:06 mschneider * Added handling of virtual properties. Needs testing. * * Revision 1.5 2006/08/22 18:14:42 mschneider * Refactored due to cleanup of org.deegree.io.datastore.schema package. * * Revision 1.4 2006/08/21 16:42:36 mschneider * Refactored due to cleanup (and splitting) of org.deegree.io.datastore.schema package. * * Revision 1.3 2006/08/21 15:44:38 mschneider * Refactored due to cleanup (and splitting) of org.deegree.io.datastore.schema package. * * Revision 1.2 2006/08/06 20:38:51 poth * never thrown exceptions and never read variables removed * * Revision 1.1 2006/05/21 19:06:21 poth * initial load up * * Revision 1.2 2006/05/12 21:17:22 polli * update and delete handler impledmented * * Revision 1.1 2006/05/09 14:51:52 polli * no message * **************************************************************************************************/