001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/io/datastore/sde/AbstractSDERequestHandler.java $ 002 /*---------------- FILE HEADER ------------------------------------------ 003 004 This file is part of deegree. 005 Copyright (C) 2006 by: M.O.S.S. Computer Grafik Systeme GmbH 006 Hohenbrunner Weg 13 007 D-82024 Taufkirchen 008 http://www.moss.de/ 009 010 This library is free software; you can redistribute it and/or 011 modify it under the terms of the GNU Lesser General Public 012 License as published by the Free Software Foundation; either 013 version 2.1 of the License, or (at your option) any later version. 014 015 This library is distributed in the hope that it will be useful, 016 but WITHOUT ANY WARRANTY; without even the implied warranty of 017 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 018 Lesser General Public License for more details. 019 020 You should have received a copy of the GNU Lesser General Public 021 License along with this library; if not, write to the Free Software 022 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 023 024 ---------------------------------------------------------------------------*/ 025 package org.deegree.io.datastore.sde; 026 027 import java.util.ArrayList; 028 import java.util.HashMap; 029 import java.util.Iterator; 030 import java.util.List; 031 import java.util.Map; 032 033 import org.deegree.framework.log.ILogger; 034 import org.deegree.framework.log.LoggerFactory; 035 import org.deegree.io.datastore.DatastoreException; 036 import org.deegree.io.datastore.FeatureId; 037 import org.deegree.io.datastore.schema.MappedFeatureType; 038 import org.deegree.io.datastore.schema.MappedGMLId; 039 import org.deegree.io.datastore.schema.MappedGeometryPropertyType; 040 import org.deegree.io.datastore.schema.MappedPropertyType; 041 import org.deegree.io.datastore.schema.MappedSimplePropertyType; 042 import org.deegree.io.datastore.schema.TableRelation; 043 import org.deegree.io.datastore.schema.content.MappingField; 044 import org.deegree.io.datastore.schema.content.SimpleContent; 045 import org.deegree.io.datastore.sql.TableAliasGenerator; 046 import org.deegree.io.sdeapi.SDEConnection; 047 import org.deegree.model.feature.schema.PropertyType; 048 import org.deegree.model.filterencoding.ComplexFilter; 049 import org.deegree.model.filterencoding.Filter; 050 051 import com.esri.sde.sdk.client.SeFilter; 052 import com.esri.sde.sdk.client.SeQuery; 053 import com.esri.sde.sdk.client.SeRow; 054 import com.esri.sde.sdk.client.SeSqlConstruct; 055 056 /** 057 * Handles <code>Transaction</code> requests to SQL based datastores. 058 * 059 * @author <a href="mailto:cpollmann@moss.de">Christoph Pollmann</a> 060 * @author last edited by: $Author: apoth $ 061 * 062 * @version $Revision: 7844 $, $Date: 2007-07-25 09:45:07 +0200 (Mi, 25 Jul 2007) $ 063 */ 064 public class AbstractSDERequestHandler { 065 066 private static final ILogger LOG = LoggerFactory.getLogger( AbstractSDERequestHandler.class ); 067 068 protected static final String FT_COLUMN = "featuretype"; 069 070 protected static final String FT_PREFIX = "ft_"; 071 072 protected SDEDatastore datastore; 073 074 protected TableAliasGenerator aliasGenerator; 075 076 protected SDEConnection conn; 077 078 /** 079 * Creates a new instance of <code>AbstractSDERequestHandler</code> from the given parameters. 080 * 081 * @param datastore 082 * @param aliasGenerator 083 * @param conn 084 */ 085 public AbstractSDERequestHandler( SDEDatastore datastore, TableAliasGenerator aliasGenerator, SDEConnection conn ) { 086 this.datastore = datastore; 087 this.aliasGenerator = aliasGenerator; 088 this.conn = conn; 089 } 090 091 /** 092 * Returns the underlying <code>AbstractSQLDatastore</code>. 093 * 094 * @return the underlying <code>AbstractSQLDatastore</code>. 095 */ 096 public SDEDatastore getDatastore() { 097 return this.datastore; 098 } 099 100 /** 101 * Returns the underlying <code>AbstractSQLDatastore</code>. 102 * 103 * @return the underlying <code>AbstractSQLDatastore</code>. 104 */ 105 public SDEConnection getConnection() { 106 return this.conn; 107 } 108 109 /** 110 * Returns the underlying <code>AbstractSQLDatastore</code>. 111 * 112 * @return the underlying <code>AbstractSQLDatastore</code>. 113 */ 114 public TableAliasGenerator getAliasGenerator() { 115 return this.aliasGenerator; 116 } 117 118 /** 119 * Determines the feature ids that are matched by the given filter. 120 * 121 * @param ft 122 * @param filter 123 * @return the feature ids that are matched by the given filter. 124 * @throws DatastoreException 125 */ 126 public FeatureId[] determineAffectedFIDs( MappedFeatureType ft, Filter filter ) 127 throws DatastoreException { 128 129 SDEWhereBuilder whereBuilder = this.datastore.getWhereBuilder( new MappedFeatureType[] { ft }, null, filter, 130 new TableAliasGenerator() ); 131 132 // if no filter is given 133 FeatureId[] fids = null; 134 SeQuery stmt = null; 135 try { 136 stmt = buildInitialFIDSelect( ft, whereBuilder ); 137 stmt.execute(); 138 fids = extractFeatureIds( stmt, ft ); 139 } catch ( Exception e ) { 140 throw new DatastoreException( "Error performing the delete transaction operation on mapped feature type '" 141 + ft.getName() + "'." ); 142 } finally { 143 try { 144 if ( stmt != null ) { 145 stmt.close(); 146 } 147 } catch ( Exception e ) { 148 } 149 } 150 return fids; 151 } 152 153 /** 154 * Builds the initial SELECT statement that retrieves the feature ids that are matched by the 155 * given <code>WhereBuilder</code>. 156 * <p> 157 * The statement is structured like this: 158 * <ul> 159 * <li><code>SELECT</code></li> 160 * <li>comma-separated list of qualified fid fields</li> 161 * <li><code>FROM</code></li> 162 * <li>comma-separated list of tables and their aliases (this is needed to constrain the paths 163 * to selected XPath-PropertyNames)</li> 164 * <li><code>WHERE</code></li> 165 * <li>SQL representation of the Filter expression</li> 166 * </ul> 167 * 168 * @param rootFeatureType 169 * @param whereBuilder 170 * @return 171 */ 172 protected SeQuery buildInitialFIDSelect( MappedFeatureType rootFeatureType, SDEWhereBuilder whereBuilder ) { 173 SeQuery query = null; 174 try { 175 StringBuffer whereCondition = new StringBuffer(); 176 whereBuilder.appendWhereCondition( whereCondition ); 177 SeSqlConstruct constr = new SeSqlConstruct( rootFeatureType.getTable(), whereCondition.toString() ); 178 String[] fidColumns = getFeatureIdColumns( rootFeatureType ); 179 query = new SeQuery( getConnection().getConnection(), fidColumns, constr ); 180 if ( whereBuilder.getFilter() instanceof ComplexFilter ) { 181 // There is NO chance, to make a new SeCoordinateReference equal to the existing crs 182 // of the requested layer. 183 // So, we give it a chance, by passing the layer definitions (and its associated 184 // crs) to the whereBuilder method 185 List layers = getConnection().getConnection().getLayers(); 186 SeFilter[] spatialFilter = whereBuilder.buildSpatialFilter( (ComplexFilter) whereBuilder.getFilter(), 187 layers ); 188 if ( null != spatialFilter && 0 < spatialFilter.length ) { 189 query.setSpatialConstraints( SeQuery.SE_OPTIMIZE, false, spatialFilter ); 190 } 191 } 192 query.prepareQuery(); 193 } catch ( Exception e ) { 194 e.printStackTrace(); 195 } 196 return query; 197 } 198 199 /** 200 * Appends the alias qualified columns that make up the feature id to the given query. 201 * 202 * @param featureType 203 */ 204 protected String[] getFeatureIdColumns( MappedFeatureType featureType ) { 205 MappingField[] fidFields = featureType.getGMLId().getIdFields(); 206 String[] columns = new String[fidFields.length]; 207 for ( int i = 0; i < fidFields.length; i++ ) { 208 columns[i] = fidFields[i].getField(); 209 } 210 return columns; 211 } 212 213 /** 214 * @param stmt 215 * @param ft 216 * @return 217 * @throws Exception 218 */ 219 public FeatureId[] extractFeatureIds( SeQuery stmt, MappedFeatureType ft ) 220 throws Exception { 221 List<FeatureId> featureIdList = new ArrayList<FeatureId>(); 222 MappedGMLId gmlId = ft.getGMLId(); 223 MappingField[] idFields = gmlId.getIdFields(); 224 SeRow row = null; 225 for ( ;; ) { 226 try { 227 row = stmt.fetch(); 228 } catch ( Exception e ) { 229 row = null; 230 } 231 if ( null == row ) 232 break; 233 Object[] idValues = new Object[idFields.length]; 234 for ( int i = 0; i < idValues.length; i++ ) { 235 idValues[i] = row.getObject( i ); 236 } 237 featureIdList.add( new FeatureId( ft, idValues ) ); 238 } 239 return featureIdList.toArray( new FeatureId[featureIdList.size()] ); 240 } 241 242 /** 243 * Builds a helper map that contains the column names of the feature type's table as keys. Each 244 * column name is mapped to a <code>List</code> containing the <code>MappingField</code> 245 * instances that refer to this column. 246 * <p> 247 * The following MappingField instances of the feature type's annotation are used to build the 248 * map: 249 * <ul> 250 * <li>MappingFields from the wfs:gmlId - annotation element of the feature type definition</li> 251 * <li>MappingFields in the annotations of the property element definitions; if the property's 252 * content is stored in a related table, the MappingFields used in the first wfs:Relation 253 * element's wfs:From element are considered</li> 254 * </ul> 255 * 256 * @param featureType 257 * feature type for which the map is built 258 * @param requestedProperties 259 * requested properties 260 * @param withIdFields 261 * @return key class: String (column names), value class: List (containing MappingField 262 * instances) 263 */ 264 protected Map<String, List<MappingField>> buildColumnsMap( MappedFeatureType featureType, 265 PropertyType[] requestedProperties, boolean withIdFields ) { 266 Map<String, List<MappingField>> columnsMap = new HashMap<String, List<MappingField>>(); 267 268 // add table columns that are necessary to build the feature's gml id 269 if ( withIdFields ) { 270 MappingField[] idFields = featureType.getGMLId().getIdFields(); 271 for ( int i = 0; i < idFields.length; i++ ) { 272 List<MappingField> mappingFieldList = columnsMap.get( idFields[i].getField() ); 273 if ( mappingFieldList == null ) { 274 mappingFieldList = new ArrayList<MappingField>(); 275 } 276 mappingFieldList.add( idFields[i] ); 277 columnsMap.put( idFields[i].getField(), mappingFieldList ); 278 } 279 } 280 281 // add columns that are necessary to build the requested feature properties 282 for ( int i = 0; i < requestedProperties.length; i++ ) { 283 MappedPropertyType pt = (MappedPropertyType) requestedProperties[i]; 284 285 TableRelation[] tableRelations = pt.getTableRelations(); 286 if ( tableRelations != null && tableRelations.length != 0 ) { 287 // if property is not stored in feature type's table, retrieve key fields of 288 // the first relation's 'From' element 289 MappingField[] fields = tableRelations[0].getFromFields(); 290 for ( int j = 0; j < fields.length; j++ ) { 291 List<MappingField> list = columnsMap.get( fields[j].getField() ); 292 if ( list == null ) { 293 list = new ArrayList<MappingField>(); 294 } 295 list.add( fields[j] ); 296 columnsMap.put( fields[j].getField(), list ); 297 } 298 // if (content instanceof FeaturePropertyContent) { 299 // if (tableRelations.length == 1) { 300 // // if feature property contains an abstract feature type, retrieve 301 // // feature type as well (stored in column named "FT_fk") 302 // MappedFeatureType subFeatureType = ( (FeaturePropertyContent) content ) 303 // .getFeatureTypeReference().getFeatureType(); 304 // if (subFeatureType.isAbstract()) { 305 // String typeColumn = FT_PREFIX + fields [0].getField(); 306 // columnsMap.put (typeColumn, new ArrayList ()); 307 // } 308 // } 309 // } 310 } else { 311 MappingField field = null; 312 if ( pt instanceof MappedSimplePropertyType ) { 313 SimpleContent content = (SimpleContent) pt; 314 if ( content instanceof MappingField ) { 315 field = (MappingField) content; 316 } else { 317 // ignore virtual properties here 318 continue; 319 } 320 } else if ( pt instanceof MappedGeometryPropertyType ) { 321 field = ( (MappedGeometryPropertyType) pt ).getMappingField(); 322 } else { 323 String msg = "Unsupported property type: '" + pt.getClass().getName() 324 + "' in QueryHandler.buildColumnsMap(). "; 325 LOG.logError( msg ); 326 throw new IllegalArgumentException( msg ); 327 } 328 List<MappingField> list = columnsMap.get( field.getField() ); 329 if ( list == null ) { 330 list = new ArrayList<MappingField>(); 331 } 332 list.add( field ); 333 columnsMap.put( field.getField(), list ); 334 } 335 } 336 return columnsMap; 337 } 338 339 /** 340 * Builds a lookup map that contains <code>MappingField</code> instances as keys. Each 341 * <code>MappingField</code> is mapped to the index position (an <code>Integer</code>) of 342 * the MappingField's field name in the given column array. 343 * 344 * @param columns 345 * @param columnsMap 346 * @return key class: MappingField, value class: Integer (index of field name in columns) 347 */ 348 protected Map<MappingField,Integer> buildMappingFieldMap( String[] columns, Map<String,List<MappingField>> columnsMap ) { 349 Map<MappingField,Integer> mappingFieldMap = new HashMap<MappingField,Integer>(); 350 for ( int i = 0; i < columns.length; i++ ) { 351 Integer resultPos = new Integer( i ); 352 List<MappingField> mappingFieldList = columnsMap.get( columns[i] ); 353 Iterator<MappingField> iter = mappingFieldList.iterator(); 354 while ( iter.hasNext() ) { 355 mappingFieldMap.put( iter.next(), resultPos ); 356 } 357 } 358 return mappingFieldMap; 359 } 360 }