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    }