001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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 }