001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_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.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: rbezema $
061 *
062 * @version $Revision: 10856 $, $Date: 2008-03-31 17:26:07 +0200 (Mo, 31 Mrz 2008) $
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 = ((MappedSimplePropertyType) pt).getContent();
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 }