001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/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.SeLayer; 053 import com.esri.sde.sdk.client.SeQuery; 054 import com.esri.sde.sdk.client.SeRow; 055 import com.esri.sde.sdk.client.SeSqlConstruct; 056 057 /** 058 * Handles <code>Transaction</code> requests to SQL based datastores. 059 * 060 * @author <a href="mailto:cpollmann@moss.de">Christoph Pollmann</a> 061 * @author last edited by: $Author: rbezema $ 062 * 063 * @version $Revision: 12183 $, $Date: 2008-06-05 11:17:23 +0200 (Do, 05 Jun 2008) $ 064 */ 065 public class AbstractSDERequestHandler { 066 067 private static final ILogger LOG = LoggerFactory.getLogger( AbstractSDERequestHandler.class ); 068 069 /** 070 * feature type column 071 */ 072 protected static final String FT_COLUMN = "featuretype"; 073 074 /** 075 * The prefix of feature types 'ft_' 076 */ 077 protected static final String FT_PREFIX = "ft_"; 078 079 /** 080 * datastore to operate upon 081 */ 082 protected SDEDatastore datastore; 083 084 /** 085 * reference to the alias generator 086 */ 087 protected TableAliasGenerator aliasGenerator; 088 089 /** 090 * a connection 091 */ 092 protected SDEConnection conn; 093 094 /** 095 * Creates a new instance of <code>AbstractSDERequestHandler</code> from the given parameters. 096 * 097 * @param datastore 098 * @param aliasGenerator 099 * @param conn 100 */ 101 public AbstractSDERequestHandler( SDEDatastore datastore, TableAliasGenerator aliasGenerator, SDEConnection conn ) { 102 this.datastore = datastore; 103 this.aliasGenerator = aliasGenerator; 104 this.conn = conn; 105 } 106 107 /** 108 * Returns the underlying <code>AbstractSQLDatastore</code>. 109 * 110 * @return the underlying <code>AbstractSQLDatastore</code>. 111 */ 112 public SDEDatastore getDatastore() { 113 return this.datastore; 114 } 115 116 /** 117 * Returns the underlying <code>AbstractSQLDatastore</code>. 118 * 119 * @return the underlying <code>AbstractSQLDatastore</code>. 120 */ 121 public SDEConnection getConnection() { 122 return this.conn; 123 } 124 125 /** 126 * Returns the underlying <code>AbstractSQLDatastore</code>. 127 * 128 * @return the underlying <code>AbstractSQLDatastore</code>. 129 */ 130 public TableAliasGenerator getAliasGenerator() { 131 return this.aliasGenerator; 132 } 133 134 /** 135 * Determines the feature ids that are matched by the given filter. 136 * 137 * @param ft 138 * @param filter 139 * @return the feature ids that are matched by the given filter. 140 * @throws DatastoreException 141 */ 142 public FeatureId[] determineAffectedFIDs( MappedFeatureType ft, Filter filter ) 143 throws DatastoreException { 144 145 SDEWhereBuilder whereBuilder = this.datastore.getWhereBuilder( new MappedFeatureType[] { ft }, null, filter, 146 new TableAliasGenerator() ); 147 148 // if no filter is given 149 FeatureId[] fids = null; 150 SeQuery stmt = null; 151 try { 152 stmt = buildInitialFIDSelect( ft, whereBuilder ); 153 stmt.execute(); 154 fids = extractFeatureIds( stmt, ft ); 155 } catch ( Exception e ) { 156 throw new DatastoreException( "Error performing the delete transaction operation on mapped feature type '" 157 + ft.getName() + "'." ); 158 } finally { 159 try { 160 if ( stmt != null ) { 161 stmt.close(); 162 } 163 } catch ( Exception e ) { 164 // eat'em keeps you healthy 165 } 166 } 167 return fids; 168 } 169 170 /** 171 * Builds the initial SELECT statement that retrieves the feature ids that are matched by the 172 * given <code>WhereBuilder</code>. 173 * <p> 174 * The statement is structured like this: 175 * <ul> 176 * <li><code>SELECT</code></li> 177 * <li>comma-separated list of qualified fid fields</li> 178 * <li><code>FROM</code></li> 179 * <li>comma-separated list of tables and their aliases (this is needed to constrain the paths 180 * to selected XPath-PropertyNames)</li> 181 * <li><code>WHERE</code></li> 182 * <li>SQL representation of the Filter expression</li> 183 * </ul> 184 * 185 * @param rootFeatureType 186 * @param whereBuilder 187 * @return the query 188 */ 189 protected SeQuery buildInitialFIDSelect( MappedFeatureType rootFeatureType, SDEWhereBuilder whereBuilder ) { 190 SeQuery query = null; 191 try { 192 StringBuffer whereCondition = new StringBuffer(); 193 whereBuilder.appendWhereCondition( whereCondition ); 194 SeSqlConstruct constr = new SeSqlConstruct( rootFeatureType.getTable(), whereCondition.toString() ); 195 String[] fidColumns = getFeatureIdColumns( rootFeatureType ); 196 query = new SeQuery( getConnection().getConnection(), fidColumns, constr ); 197 if ( whereBuilder.getFilter() instanceof ComplexFilter ) { 198 // There is NO chance, to make a new SeCoordinateReference equal to the existing crs 199 // of the requested layer. 200 // So, we give it a chance, by passing the layer definitions (and its associated 201 // crs) to the whereBuilder method 202 List<SeLayer> layers = getConnection().getConnection().getLayers(); 203 SeFilter[] spatialFilter = whereBuilder.buildSpatialFilter( (ComplexFilter) whereBuilder.getFilter(), 204 layers ); 205 if ( null != spatialFilter && 0 < spatialFilter.length ) { 206 query.setSpatialConstraints( SeQuery.SE_OPTIMIZE, false, spatialFilter ); 207 } 208 } 209 query.prepareQuery(); 210 } catch ( Exception e ) { 211 e.printStackTrace(); 212 } 213 return query; 214 } 215 216 /** 217 * Appends the alias qualified columns that make up the feature id to the given query. 218 * 219 * @param featureType 220 * @return the feature id columns 221 */ 222 protected String[] getFeatureIdColumns( MappedFeatureType featureType ) { 223 MappingField[] fidFields = featureType.getGMLId().getIdFields(); 224 String[] columns = new String[fidFields.length]; 225 for ( int i = 0; i < fidFields.length; i++ ) { 226 columns[i] = fidFields[i].getField(); 227 } 228 return columns; 229 } 230 231 /** 232 * @param stmt 233 * @param ft 234 * @return the ids 235 * @throws Exception 236 */ 237 public FeatureId[] extractFeatureIds( SeQuery stmt, MappedFeatureType ft ) 238 throws Exception { 239 List<FeatureId> featureIdList = new ArrayList<FeatureId>(); 240 MappedGMLId gmlId = ft.getGMLId(); 241 MappingField[] idFields = gmlId.getIdFields(); 242 SeRow row = null; 243 for ( ;; ) { 244 try { 245 row = stmt.fetch(); 246 } catch ( Exception e ) { 247 row = null; 248 } 249 if ( null == row ) 250 break; 251 Object[] idValues = new Object[idFields.length]; 252 for ( int i = 0; i < idValues.length; i++ ) { 253 idValues[i] = row.getObject( i ); 254 } 255 featureIdList.add( new FeatureId( ft, idValues ) ); 256 } 257 return featureIdList.toArray( new FeatureId[featureIdList.size()] ); 258 } 259 260 /** 261 * Builds a helper map that contains the column names of the feature type's table as keys. Each 262 * column name is mapped to a <code>List</code> containing the <code>MappingField</code> 263 * instances that refer to this column. 264 * <p> 265 * The following MappingField instances of the feature type's annotation are used to build the 266 * map: 267 * <ul> 268 * <li>MappingFields from the wfs:gmlId - annotation element of the feature type definition</li> 269 * <li>MappingFields in the annotations of the property element definitions; if the property's 270 * content is stored in a related table, the MappingFields used in the first wfs:Relation 271 * element's wfs:From element are considered</li> 272 * </ul> 273 * 274 * @param featureType 275 * feature type for which the map is built 276 * @param requestedProperties 277 * requested properties 278 * @param withIdFields 279 * @return key class: String (column names), value class: List (containing MappingField 280 * instances) 281 */ 282 protected Map<String, List<MappingField>> buildColumnsMap( MappedFeatureType featureType, 283 PropertyType[] requestedProperties, boolean withIdFields ) { 284 Map<String, List<MappingField>> columnsMap = new HashMap<String, List<MappingField>>(); 285 286 // add table columns that are necessary to build the feature's gml id 287 if ( withIdFields ) { 288 MappingField[] idFields = featureType.getGMLId().getIdFields(); 289 for ( int i = 0; i < idFields.length; i++ ) { 290 List<MappingField> mappingFieldList = columnsMap.get( idFields[i].getField() ); 291 if ( mappingFieldList == null ) { 292 mappingFieldList = new ArrayList<MappingField>(); 293 } 294 mappingFieldList.add( idFields[i] ); 295 columnsMap.put( idFields[i].getField(), mappingFieldList ); 296 } 297 } 298 299 // add columns that are necessary to build the requested feature properties 300 for ( int i = 0; i < requestedProperties.length; i++ ) { 301 MappedPropertyType pt = (MappedPropertyType) requestedProperties[i]; 302 303 TableRelation[] tableRelations = pt.getTableRelations(); 304 if ( tableRelations != null && tableRelations.length != 0 ) { 305 // if property is not stored in feature type's table, retrieve key fields of 306 // the first relation's 'From' element 307 MappingField[] fields = tableRelations[0].getFromFields(); 308 for ( int j = 0; j < fields.length; j++ ) { 309 List<MappingField> list = columnsMap.get( fields[j].getField() ); 310 if ( list == null ) { 311 list = new ArrayList<MappingField>(); 312 } 313 list.add( fields[j] ); 314 columnsMap.put( fields[j].getField(), list ); 315 } 316 // if (content instanceof FeaturePropertyContent) { 317 // if (tableRelations.length == 1) { 318 // // if feature property contains an abstract feature type, retrieve 319 // // feature type as well (stored in column named "FT_fk") 320 // MappedFeatureType subFeatureType = ( (FeaturePropertyContent) content ) 321 // .getFeatureTypeReference().getFeatureType(); 322 // if (subFeatureType.isAbstract()) { 323 // String typeColumn = FT_PREFIX + fields [0].getField(); 324 // columnsMap.put (typeColumn, new ArrayList ()); 325 // } 326 // } 327 // } 328 } else { 329 MappingField field = null; 330 if ( pt instanceof MappedSimplePropertyType ) { 331 SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent(); 332 if ( content instanceof MappingField ) { 333 field = (MappingField) content; 334 } else { 335 // ignore virtual properties here 336 continue; 337 } 338 } else if ( pt instanceof MappedGeometryPropertyType ) { 339 field = ( (MappedGeometryPropertyType) pt ).getMappingField(); 340 } else { 341 String msg = "Unsupported property type: '" + pt.getClass().getName() 342 + "' in QueryHandler.buildColumnsMap(). "; 343 LOG.logError( msg ); 344 throw new IllegalArgumentException( msg ); 345 } 346 List<MappingField> list = columnsMap.get( field.getField() ); 347 if ( list == null ) { 348 list = new ArrayList<MappingField>(); 349 } 350 list.add( field ); 351 columnsMap.put( field.getField(), list ); 352 } 353 } 354 return columnsMap; 355 } 356 357 /** 358 * Builds a lookup map that contains <code>MappingField</code> instances as keys. Each 359 * <code>MappingField</code> is mapped to the index position (an <code>Integer</code>) of 360 * the MappingField's field name in the given column array. 361 * 362 * @param columns 363 * @param columnsMap 364 * @return key class: MappingField, value class: Integer (index of field name in columns) 365 */ 366 protected Map<MappingField, Integer> buildMappingFieldMap( String[] columns, 367 Map<String, List<MappingField>> columnsMap ) { 368 Map<MappingField, Integer> mappingFieldMap = new HashMap<MappingField, Integer>(); 369 for ( int i = 0; i < columns.length; i++ ) { 370 Integer resultPos = new Integer( i ); 371 List<MappingField> mappingFieldList = columnsMap.get( columns[i] ); 372 Iterator<MappingField> iter = mappingFieldList.iterator(); 373 while ( iter.hasNext() ) { 374 mappingFieldMap.put( iter.next(), resultPos ); 375 } 376 } 377 return mappingFieldMap; 378 } 379 }