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    }