001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/io/datastore/sde/SDEInsertHandler.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.Collection;
029    import java.util.HashMap;
030    import java.util.Iterator;
031    import java.util.List;
032    import java.util.Map;
033    
034    import org.deegree.datatypes.Types;
035    import org.deegree.framework.log.ILogger;
036    import org.deegree.framework.log.LoggerFactory;
037    import org.deegree.framework.util.StringTools;
038    import org.deegree.io.datastore.DatastoreException;
039    import org.deegree.io.datastore.FeatureId;
040    import org.deegree.io.datastore.TransactionException;
041    import org.deegree.io.datastore.idgenerator.FeatureIdAssigner;
042    import org.deegree.io.datastore.idgenerator.IdGenerationException;
043    import org.deegree.io.datastore.idgenerator.ParentIDGenerator;
044    import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
045    import org.deegree.io.datastore.schema.MappedFeatureType;
046    import org.deegree.io.datastore.schema.MappedGeometryPropertyType;
047    import org.deegree.io.datastore.schema.MappedPropertyType;
048    import org.deegree.io.datastore.schema.MappedSimplePropertyType;
049    import org.deegree.io.datastore.schema.TableRelation;
050    import org.deegree.io.datastore.schema.content.MappingField;
051    import org.deegree.io.datastore.schema.content.SimpleContent;
052    import org.deegree.io.datastore.sql.transaction.insert.FeatureRow;
053    import org.deegree.io.datastore.sql.transaction.insert.InsertField;
054    import org.deegree.io.datastore.sql.transaction.insert.InsertRow;
055    import org.deegree.io.sdeapi.SDEAdapter;
056    import org.deegree.model.feature.Feature;
057    import org.deegree.model.feature.FeatureProperty;
058    import org.deegree.model.feature.schema.FeaturePropertyType;
059    import org.deegree.model.feature.schema.GeometryPropertyType;
060    import org.deegree.model.feature.schema.SimplePropertyType;
061    import org.deegree.model.spatialschema.Geometry;
062    
063    import com.esri.sde.sdk.client.SeException;
064    import com.esri.sde.sdk.client.SeInsert;
065    import com.esri.sde.sdk.client.SeObjectId;
066    import com.esri.sde.sdk.client.SeQuery;
067    import com.esri.sde.sdk.client.SeRow;
068    import com.esri.sde.sdk.client.SeSqlConstruct;
069    import com.esri.sde.sdk.client.SeState;
070    
071    /**
072     * Handler for <code>Insert</code> operations (usually contained in <code>Transaction</code> requests).
073     * 
074     * @author <a href="mailto:cpollmann@moss.de">Christoph Pollmann</a>
075     * @author last edited by: $Author: aschmitz $
076     * 
077     * @version $Revision: 23585 $
078     */
079    public class SDEInsertHandler extends AbstractSDERequestHandler {
080    
081        private static final ILogger LOG = LoggerFactory.getLogger( SDEInsertHandler.class );
082    
083        // features that are currently being processed
084        private Map<FeatureId, FeatureRow> featuresInInsertion = new HashMap<FeatureId, FeatureRow>();
085    
086        // contains only property rows and join table rows (but no feature rows)
087        private List<InsertRow> insertRows = new ArrayList<InsertRow>();
088    
089        private SDETransaction dsTa;
090    
091        /**
092         * Creates a new <code>InsertHandler</code> from the given parameters.
093         * 
094         * @param dsTa
095         */
096        public SDEInsertHandler( SDETransaction dsTa ) {
097            super( dsTa.getDatastore(), dsTa.getAliasGenerator(), dsTa.getConnection() );
098            this.dsTa = dsTa;
099        }
100    
101        /**
102         * Inserts the given feature instance into the datastore.
103         * 
104         * @param features
105         *            (which have a MappedFeatureType as feature type)
106         * @return feature ids of inserted (root) feature instances
107         * @throws DatastoreException
108         *             if the insert could not be performed
109         */
110        public List<FeatureId> performInsert( List<Feature> features )
111                                throws DatastoreException {
112    
113            List<FeatureId> fids = new ArrayList<FeatureId>();
114    
115            for ( int i = 0; i < features.size(); i++ ) {
116                Feature feature = features.get( i );
117                MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
118                if ( feature.getId().startsWith( FeatureIdAssigner.EXISTS_MARKER ) ) {
119                    String msg = "feature already exists " + feature.getName() + " id=" + feature.getId().substring( 1 );
120                    throw new TransactionException( msg );
121                }
122                LOG.logDebug( "Inserting root feature '" + feature.getId() + "'..." );
123                insertFeature( feature );
124                FeatureId fid = new FeatureId( ft, feature.getId() );
125                fids.add( fid );
126            }
127    
128            // merge inserts rows that are identical (except their pks)
129            this.insertRows = mergeInsertRows( this.insertRows );
130    
131            // add featureRows to insertRows
132            Iterator<FeatureRow> iter = this.featuresInInsertion.values().iterator();
133            while ( iter.hasNext() ) {
134                this.insertRows.add( iter.next() );
135            }
136    
137            // try to sort the insert rows topologically (but continue in original order, if not topological order is
138            // possible)
139            List<InsertRow> sortedInserts = InsertRow.getInsertOrder( this.insertRows );
140    
141            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
142                Iterator<InsertRow> iter2 = sortedInserts.iterator();
143                LOG.logDebug( sortedInserts.size() + " rows to be inserted: " );
144                while ( iter2.hasNext() ) {
145                    LOG.logDebug( iter2.next().toString() );
146                }
147            }
148    
149            executeInserts( sortedInserts );
150    
151            return fids;
152        }
153    
154        /**
155         * Builds the <code>InsertRows</code> that are necessary to insert the given feature instance (including all
156         * properties + subfeatures).
157         * 
158         * @param feature
159         * @return the row of the given feature
160         * @throws TransactionException
161         */
162        private FeatureRow insertFeature( Feature feature )
163                                throws TransactionException {
164    
165            MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
166            if ( !ft.isInsertable() ) {
167                String msg = "featuretype can't be inserted " + ft.getName();
168                throw new TransactionException( msg );
169            }
170    
171            LOG.logDebug( "Creating InsertRow for feature with type '" + ft.getName() + "' and id: '" + feature.getId()
172                          + "'." );
173    
174            // extract feature id column value
175            MappingField[] fidFields = ft.getGMLId().getIdFields();
176            if ( fidFields.length > 1 ) {
177                throw new TransactionException( "Insertion of features with compound feature ids is not " + "supported." );
178            }
179            Object fidValue = null;
180            try {
181                fidValue = FeatureId.removeFIDPrefix( feature.getId(), ft.getGMLId() );
182            } catch ( DatastoreException e ) {
183                e.printStackTrace();
184                throw new TransactionException( e.getMessage(), e );
185            }
186            FeatureId fid = new FeatureId( ft, new Object[] { fidValue } );
187    
188            // check if the feature id is already being inserted (happens for cyclic features)
189            FeatureRow insertRow = this.featuresInInsertion.get( fid );
190            if ( insertRow != null ) {
191                return insertRow;
192            }
193    
194            insertRow = new FeatureRow( ft.getTable() );
195            this.featuresInInsertion.put( fid, insertRow );
196    
197            // add column value for fid (primary key)
198            String fidColumn = fidFields[0].getField();
199            insertRow.setColumn( fidColumn, fidValue, ft.getGMLId().getIdFields()[0].getType(), true );
200    
201            // process properties
202            FeatureProperty[] properties = feature.getProperties();
203            for ( int i = 0; i < properties.length; i++ ) {
204                FeatureProperty property = properties[i];
205                MappedPropertyType propertyType = (MappedPropertyType) ft.getProperty( property.getName() );
206                if ( propertyType == null ) {
207                    String msg = "Unknown propertytype " + property.getName();
208                    LOG.logDebug( msg );
209                    throw new TransactionException( msg );
210                }
211                insertProperty( property, propertyType, insertRow );
212            }
213            return insertRow;
214        }
215    
216        /**
217         * Builds the <code>InsertRow</code>s that are necessary to insert the given property instance (including all it's
218         * subfeatures).
219         * 
220         * @param property
221         *            property instance to be inserted
222         * @param propertyType
223         *            property type of the property
224         * @param featureRow
225         *            table row of the parent feature instance
226         * @throws TransactionException
227         */
228        private void insertProperty( FeatureProperty property, MappedPropertyType propertyType, InsertRow featureRow )
229                                throws TransactionException {
230    
231            if ( propertyType instanceof SimplePropertyType ) {
232                String msg = StringTools.concat( 300, "- Simple property '", propertyType.getName(),
233                                                 "', value='" + getPropertyValue( property ), "'." );
234                LOG.logDebug( msg );
235                insertProperty( (MappedSimplePropertyType) propertyType, property, featureRow );
236            } else if ( propertyType instanceof GeometryPropertyType ) {
237                LOG.logDebug( "- Geometry property: '" + propertyType.getName() + "'" );
238                insertProperty( (MappedGeometryPropertyType) propertyType, property, featureRow );
239            } else if ( propertyType instanceof FeaturePropertyType ) {
240                LOG.logDebug( "- Feature property: '" + propertyType.getName() + "'" );
241                insertProperty( (MappedFeaturePropertyType) propertyType, property, featureRow );
242            } else {
243                throw new TransactionException( "Unhandled property type '" + propertyType.getClass().getName() + "'." );
244            }
245        }
246    
247        /**
248         * Inserts the given simple property (stored in feature table or in related table).
249         * 
250         * @param pt
251         * @param property
252         * @param featureRow
253         * @throws TransactionException
254         */
255        private void insertProperty( MappedSimplePropertyType pt, FeatureProperty property, InsertRow featureRow )
256                                throws TransactionException {
257    
258            SimpleContent content = pt.getContent();
259            if ( content.isUpdateable() ) {
260                if ( content instanceof MappingField ) {
261                    MappingField mf = (MappingField) content;
262                    String propertyColumn = mf.getField();
263                    Object propertyValue = property.getValue();
264                    int propertyType = mf.getType();
265                    TableRelation[] relations = pt.getTableRelations();
266                    insertProperty( propertyColumn, propertyValue, propertyType, relations, featureRow );
267                }
268            }
269        }
270    
271        /**
272         * Inserts the given geometry property (stored in feature table or in related table).
273         * 
274         * @param pt
275         * @param property
276         * @param featureRow
277         * @throws TransactionException
278         */
279        private void insertProperty( MappedGeometryPropertyType pt, FeatureProperty property, InsertRow featureRow )
280                                throws TransactionException {
281    
282            String propertyColumn = pt.getMappingField().getField();
283    
284            Geometry deegreeGeometry = (Geometry) property.getValue();
285            Object dbGeometry;
286    
287            try {
288                dbGeometry = this.datastore.convertDegreeToDBGeometry( deegreeGeometry );
289            } catch ( DatastoreException e ) {
290                throw new TransactionException( e.getMessage(), e );
291            }
292    
293            int propertyType = pt.getMappingField().getType();
294    
295            TableRelation[] relations = pt.getTableRelations();
296            insertProperty( propertyColumn, dbGeometry, propertyType, relations, featureRow );
297        }
298    
299        /**
300         * Inserts the given simple / geometry property (stored in feature table or in related table).
301         * 
302         * @param propertyColumn
303         * @param propertyValue
304         * @param propertyType
305         * @param featureRow
306         * @throws TransactionException
307         */
308        private void insertProperty( String propertyColumn, Object propertyValue, int propertyType,
309                                     TableRelation[] relations, InsertRow featureRow )
310                                throws TransactionException {
311    
312            if ( relations == null || relations.length == 0 ) {
313                // property is stored in feature table
314                featureRow.setColumn( propertyColumn, propertyValue, propertyType, false );
315            } else {
316                // property is stored in related table
317                if ( relations.length > 1 ) {
318                    throw new TransactionException( "properties in related tables are not allowed here" );
319                }
320    
321                if ( !relations[0].isFromFK() ) {
322                    // fk is in property table
323                    MappingField[] pkFields = relations[0].getFromFields();
324                    MappingField[] fkFields = relations[0].getToFields();
325    
326                    for ( int i = 0; i < pkFields.length; i++ ) {
327                        InsertField pkField = featureRow.getColumn( pkFields[i].getField() );
328                        if ( pkField == null ) {
329                            String msg = "Missing foreign key " + pkFields[i].getField() + " / " + pkFields[i].getTable();
330                            throw new TransactionException( msg );
331                        }
332                        int pkColumnType = pkField.getSQLType();
333                        int fkColumnType = fkFields[i].getType();
334                        if ( pkColumnType != fkColumnType ) {
335                            String msg = "FK_PK_TYPE_MISMATCH";
336                            throw new TransactionException( msg );
337                        }
338                        InsertRow insertRow = new InsertRow( relations[0].getToTable() );
339                        insertRow.linkColumn( fkFields[i].getField(), pkField );
340                        insertRow.setColumn( propertyColumn, propertyValue, propertyType, false );
341                        this.insertRows.add( insertRow );
342                    }
343                } else {
344                    // fk is in feature table
345                    MappingField[] pkFields = relations[0].getToFields();
346                    MappingField[] fkFields = relations[0].getFromFields();
347    
348                    // generate necessary primary key value
349                    InsertField pkField = null;
350                    try {
351                        Object pk = null;
352                        // TODO remove hack!!!
353                        if ( relations[0].getIdGenerator() instanceof ParentIDGenerator ) {
354                            InsertField field = featureRow.getColumn( "ID" );
355                            if ( field == null ) {
356                                throw new TransactionException( "No value for ID available!" );
357                            }
358                            pk = field.getValue();
359                        } else {
360                            pk = relations[0].getNewPK( this.dsTa );
361                        }
362                        InsertRow insertRow = findOrCreateRow( relations[0].getToTable(), pkFields[0].getField(), pk );
363                        pkField = insertRow.setColumn( pkFields[0].getField(), pk, pkFields[0].getType(), true );
364                        insertRow.setColumn( propertyColumn, propertyValue, propertyType, false );
365                    } catch ( IdGenerationException e ) {
366                        throw new TransactionException( e.getMessage(), e );
367                    }
368                    featureRow.linkColumn( fkFields[0].getField(), pkField );
369                }
370            }
371        }
372    
373        /**
374         * Inserts the given feature property.
375         * 
376         * @param pt
377         * @param property
378         * @param featureRow
379         * @throws TransactionException
380         */
381        private void insertProperty( MappedFeaturePropertyType pt, FeatureProperty property, InsertRow featureRow )
382                                throws TransactionException {
383    
384            // find (concrete) subfeature type for the given property instance
385            MappedFeatureType propertyFeatureType = pt.getFeatureTypeReference().getFeatureType();
386            MappedFeatureType[] substitutions = propertyFeatureType.getConcreteSubstitutions();
387            Feature subFeature = (Feature) property.getValue();
388            MappedFeatureType subFeatureType = null;
389            for ( int i = 0; i < substitutions.length; i++ ) {
390                if ( substitutions[i].getName().equals( subFeature.getName() ) ) {
391                    subFeatureType = substitutions[i];
392                    break;
393                }
394            }
395            if ( subFeatureType == null ) {
396                String msg = "ERROR_FEATURE_NOT_SUBSTITUTABLE " + propertyFeatureType.getName() + "->"
397                             + subFeature.getName();
398                throw new TransactionException( msg );
399            }
400            boolean ftIsAbstract = propertyFeatureType.isAbstract();
401    
402            TableRelation[] relations = pt.getTableRelations();
403            if ( relations == null || relations.length < 1 ) {
404                throw new TransactionException( "Invalid feature property definition, feature property "
405                                                + "mappings must use at least one 'TableRelation' element." );
406            }
407    
408            // workaround for links to dummy InsertRows (of already stored features)
409            boolean cutLink = subFeature.getId().startsWith( FeatureIdAssigner.EXISTS_MARKER );
410            InsertRow subFeatureRow = null;
411            if ( cutLink ) {
412                try {
413                    Object fidValue = FeatureId.removeFIDPrefix( subFeature.getId().substring( 1 ),
414                                                                 subFeatureType.getGMLId() );
415                    subFeatureRow = new FeatureRow( subFeatureType.getTable() );
416                    // add column value for fid (primary key)
417                    String fidColumn = subFeatureType.getGMLId().getIdFields()[0].getField();
418                    subFeatureRow.setColumn( fidColumn, fidValue, subFeatureType.getGMLId().getIdFields()[0].getType(),
419                                             true );
420                } catch ( DatastoreException e ) {
421                    throw new TransactionException( e );
422                }
423            } else {
424                // insert sub feature (if it is not already stored)
425                subFeatureRow = insertFeature( subFeature );
426            }
427    
428            if ( relations.length == 1 ) {
429    
430                if ( relations[0].isFromFK() ) {
431                    // fk is in feature table
432                    MappingField[] pkFields = relations[0].getToFields();
433                    MappingField[] fkFields = relations[0].getFromFields();
434    
435                    for ( int i = 0; i < pkFields.length; i++ ) {
436                        InsertField pkField = subFeatureRow.getColumn( pkFields[i].getField() );
437                        if ( pkField == null ) {
438                            String msg = "Missing foreign key.";
439                            throw new TransactionException( msg );
440                        }
441                        int pkColumnType = pkField.getSQLType();
442                        int fkColumnType = fkFields[i].getType();
443                        if ( pkColumnType != fkColumnType ) {
444                            String msg = "ERROR_FK_PK_TYPE_MISMATCH";
445                            throw new TransactionException( msg );
446                        }
447    
448                        if ( !cutLink ) {
449                            featureRow.linkColumn( fkFields[i].getField(), pkField );
450                        } else {
451                            featureRow.setColumn( fkFields[i].getField(), pkField.getValue(), pkField.getSQLType(), false );
452                        }
453    
454                    }
455    
456                    if ( ftIsAbstract ) {
457                        String typeField = FT_PREFIX + relations[0].getToTable();
458                        featureRow.setColumn( typeField, subFeatureType.getName().getLocalName(), Types.VARCHAR, false );
459                    }
460                } else {
461                    // fk is in subfeature table
462                    MappingField[] pkFields = relations[0].getFromFields();
463                    MappingField[] fkFields = relations[0].getToFields();
464    
465                    InsertField pkField = featureRow.getColumn( pkFields[0].getField() );
466    
467                    if ( pkField == null ) {
468                        String msg = "Missing foreign key.";
469                        throw new TransactionException( msg );
470                    }
471                    int pkColumnType = pkField.getSQLType();
472                    int fkColumnType = fkFields[0].getType();
473                    if ( pkColumnType != fkColumnType ) {
474                        String msg = "ERROR_FK_PK_TYPE_MISMATCH";
475                        throw new TransactionException( msg );
476                    }
477    
478                    if ( !cutLink ) {
479                        subFeatureRow.linkColumn( fkFields[0].getField(), pkField );
480                    } else {
481                        featureRow.setColumn( fkFields[0].getField(), pkField.getValue(), pkField.getSQLType(), false );
482                    }
483                }
484            } else if ( relations.length == 2 ) {
485    
486                // insert into join table
487                String joinTable = relations[0].getToTable();
488                MappingField[] leftKeyFields = relations[0].getToFields();
489                MappingField[] rightKeyFields = relations[1].getFromFields();
490    
491                InsertRow jtRow = new InsertRow( joinTable );
492                if ( ftIsAbstract ) {
493                    jtRow.setColumn( FT_COLUMN, subFeatureType.getName().getLocalName(), Types.VARCHAR, false );
494                }
495    
496                if ( !relations[0].isFromFK() ) {
497                    // left key field in join table is fk
498                    MappingField[] pkFields = relations[0].getFromFields();
499                    InsertField pkField = featureRow.getColumn( pkFields[0].getField() );
500                    if ( pkField == null ) {
501                        throw new TransactionException( "Insertion of feature property using join table failed: "
502                                                        + "no value for join table key column." );
503                    }
504                    jtRow.linkColumn( leftKeyFields[0].getField(), pkField );
505                } else {
506                    // left key field in join table is pk
507                    MappingField[] pkFields = relations[0].getToFields();
508                    // generate necessary primary key value
509                    InsertField pkField = null;
510                    try {
511                        Object pk = relations[0].getNewPK( this.dsTa );
512                        pkField = jtRow.setColumn( pkFields[0].getField(), pk, pkFields[0].getType(), true );
513                    } catch ( IdGenerationException e ) {
514                        throw new TransactionException( e.getMessage(), e );
515                    }
516                    featureRow.linkColumn( relations[0].getFromFields()[0].getField(), pkField );
517                }
518    
519                if ( relations[1].isFromFK() ) {
520                    // right key field in join table is fk
521                    MappingField[] pkFields = relations[1].getToFields();
522                    InsertField pkField = subFeatureRow.getColumn( pkFields[0].getField() );
523                    if ( pkField == null ) {
524                        throw new TransactionException( "Insertion of feature property using join table failed: "
525                                                        + "no value for join table key column." );
526                    }
527                    if ( !cutLink ) {
528                        jtRow.linkColumn( rightKeyFields[0].getField(), pkField );
529                    } else {
530                        jtRow.setColumn( rightKeyFields[0].getField(), pkField.getValue(), pkField.getSQLType(), false );
531                    }
532                } else {
533                    // right key field in join table is pk
534                    MappingField[] pkFields = relations[1].getFromFields();
535                    // generate necessary primary key value
536                    InsertField pkField = null;
537                    try {
538                        Object pk = relations[1].getNewPK( this.dsTa );
539                        pkField = jtRow.setColumn( pkFields[0].getField(), pk, pkFields[0].getType(), true );
540                    } catch ( IdGenerationException e ) {
541                        throw new TransactionException( e.getMessage(), e );
542                    }
543                    if ( !cutLink ) {
544                        subFeatureRow.linkColumn( relations[1].getToFields()[0].getField(), pkField );
545                    }
546                }
547    
548                this.insertRows.add( jtRow );
549            } else {
550                throw new TransactionException( "Insertion of feature properties stored in related tables "
551                                                + "connected via more than one join table is not supported." );
552            }
553        }
554    
555        /**
556         * Checks whether the feature that corresponds to the given FeatureRow is already stored in the database.
557         * 
558         * @param featureRow
559         * @return true if the feature exists
560         * @throws TransactionException
561         */
562        private boolean doesFeatureExist( FeatureRow featureRow )
563                                throws TransactionException {
564    
565            boolean exists = false;
566    
567            InsertField pkField = featureRow.getPKColumn();
568    
569            SeQuery stmt = null;
570            try {
571                stmt = buildFeatureSelect( pkField.getColumnName(), pkField.getValue(), featureRow.getTable() );
572                stmt.execute();
573                SeRow row = stmt.fetch();
574                if ( null != row ) {
575                    exists = true;
576                }
577                row = stmt.fetch();
578                if ( null != row ) {
579                    String msg = "ERROR_FEATURE_QUERY_MORE_THAN_ONE_RESULT";
580                    LOG.logError( msg );
581                    throw new TransactionException( msg );
582                }
583            } catch ( Exception e ) {
584                throw new TransactionException( e );
585            } finally {
586                try {
587                    stmt.close();
588                } catch ( Exception e ) {
589                    LOG.logDebug( "Error in SDE command", e );
590                }
591            }
592            return exists;
593        }
594    
595        /**
596         * Builds a SELECT statement that checks for the existence of a feature with the given id.
597         * 
598         * @param fidColumn
599         * @param fidValue
600         * @param table
601         * @return the statement
602         */
603        private SeQuery buildFeatureSelect( String fidColumn, Object fidValue, String table ) {
604            SeQuery query = null;
605            try {
606                SeSqlConstruct constr = new SeSqlConstruct( table, fidColumn + "='" + fidValue.toString() + "'" );
607                String[] columns = new String[1];
608                columns[0] = fidColumn;
609                query = new SeQuery( getConnection().getConnection(), columns, constr );
610                query.setState( getConnection().getState().getId(), new SeObjectId( SeState.SE_NULL_STATE_ID ),
611                                SeState.SE_STATE_DIFF_NOCHECK );
612                query.prepareQuery();
613            } catch ( Exception e ) {
614                LOG.logError( "Error building featureSelect", e );
615            }
616            return query;
617        }
618    
619        private InsertRow findOrCreateRow( String table, String pkColumn, Object value ) {
620            Iterator<InsertRow> rowIter = this.insertRows.iterator();
621            boolean found = false;
622            InsertRow row = null;
623            while ( rowIter.hasNext() ) {
624                row = rowIter.next();
625                if ( row.getTable().equals( table ) ) {
626                    InsertField field = row.getColumn( pkColumn );
627                    if ( value.equals( field.getValue() ) ) {
628                        found = true;
629                        LOG.logDebug( "Found matching row " + row );
630                        break;
631                    }
632                }
633            }
634            if ( !found ) {
635                row = new InsertRow( table );
636                this.insertRows.add( row );
637            }
638            return row;
639        }
640    
641        private String getPropertyValue( FeatureProperty property ) {
642            Object value = property.getValue();
643            StringBuffer sb = new StringBuffer();
644            if ( value instanceof Object[] ) {
645                Object[] objects = (Object[]) value;
646                for ( int i = 0; i < objects.length; i++ ) {
647                    sb.append( objects[i] );
648                }
649            } else {
650                sb.append( value );
651            }
652            return sb.toString();
653        }
654    
655        /**
656         * Transforms the given <code>List</code> of <code>InsertRows</code> into SQL INSERT statements and executes them
657         * using the underlying JDBC connection.
658         * 
659         * @param inserts
660         * @throws TransactionException
661         *             if an SQL error occurs
662         */
663        private void executeInserts( List<InsertRow> inserts )
664                                throws TransactionException {
665    
666            SeInsert stmt = null;
667    
668            for ( InsertRow row : inserts ) {
669                if ( row instanceof FeatureRow ) {
670                    if ( doesFeatureExist( (FeatureRow) row ) ) {
671                        LOG.logDebug( "Skipping feature row. Already present in db." );
672                        continue;
673                    }
674                }
675                try {
676                    stmt = createStatement( row );
677                    stmt.execute();
678                } catch ( Exception e ) {
679                    String msg = "Error performing insert: " + e.getMessage();
680                    LOG.logError( msg, e );
681                    throw new TransactionException( msg, e );
682                } finally {
683                    if ( stmt != null ) {
684                        try {
685                            stmt.close();
686                        } catch ( Exception e ) {
687                            String msg = "Error closing statement: " + e.getMessage();
688                            LOG.logError( msg, e );
689                        }
690                    }
691                }
692            }
693        }
694    
695        private SeInsert createStatement( InsertRow row )
696                                throws SeException {
697            SeInsert inserter = new SeInsert( conn.getConnection() );
698            inserter.setState( conn.getState().getId(), new SeObjectId( SeState.SE_NULL_STATE_ID ),
699                               SeState.SE_STATE_DIFF_NOCHECK );
700            Collection<InsertField> fields = row.getColumns();
701            String[] columns = new String[fields.size()];
702            int i = 0;
703            for ( Iterator<InsertField> iter = fields.iterator(); iter.hasNext(); i++ ) {
704                InsertField field = iter.next();
705                columns[i] = field.getColumnName();
706            }
707            inserter.intoTable( row.getTable(), columns );
708            SeRow insertRow = inserter.getRowToSet();
709            for ( i = 0; i < columns.length; i++ ) {
710                InsertField field = row.getColumn( columns[i] );
711                SDEAdapter.setRowValue( insertRow, i, field.getValue(), SDEAdapter.mapSQL2SDE( field.getSQLType() ) );
712            }
713            return inserter;
714        }
715    
716        /**
717         * Merges the given <code>InsertRow</code>s by eliminating rows that have identical content (except for their
718         * primary keys).
719         * <p>
720         * This only applies to non-FeatureRows: there are never two FeatureRows that may be treated as identical, because
721         * unique feature ids have been assigned to them before.
722         * 
723         * @see FeatureIdAssigner
724         * 
725         * @param insertRows
726         * @return the cleaned up list
727         */
728        private List<InsertRow> mergeInsertRows( List<InsertRow> insertRows ) {
729    
730            List<InsertRow> result = new ArrayList<InsertRow>();
731    
732            // keys: table names, values: inserts into the table
733            Map<String, Collection<InsertRow>> tableMap = new HashMap<String, Collection<InsertRow>>();
734    
735            // build table lookup map
736            Iterator<InsertRow> iter = insertRows.iterator();
737            while ( iter.hasNext() ) {
738                InsertRow insertRow = iter.next();
739                Collection<InsertRow> tableInserts = tableMap.get( insertRow.getTable() );
740                if ( tableInserts == null ) {
741                    tableInserts = new ArrayList<InsertRow>();
742                    tableMap.put( insertRow.getTable(), tableInserts );
743                }
744                tableInserts.add( insertRow );
745            }
746    
747            iter = insertRows.iterator();
748            while ( iter.hasNext() ) {
749                InsertRow insertRow = iter.next();
750                boolean insert = true;
751                if ( !( insertRow instanceof FeatureRow ) ) {
752                    Collection<InsertRow> tableInserts = tableMap.get( insertRow.getTable() );
753                    Iterator<InsertRow> candidatesIter = tableInserts.iterator();
754                    while ( candidatesIter.hasNext() ) {
755                        InsertRow candidate = candidatesIter.next();
756                        if ( insertRow != candidate ) {
757                            if ( compareInsertRows( insertRow, candidate ) ) {
758                                LOG.logDebug( "Removing InsertRow: " + insertRow.hashCode() + " " + insertRow
759                                              + " - duplicate of: " + candidate );
760                                replaceInsertRow( insertRow, candidate );
761                                insert = false;
762                                tableInserts.remove( insertRow );
763                                break;
764                            }
765                        }
766                    }
767                }
768                if ( insert ) {
769                    result.add( insertRow );
770                }
771            }
772            return result;
773        }
774    
775        private boolean compareInsertRows( InsertRow row1, InsertRow row2 ) {
776            Collection<InsertField> fields1 = row1.getColumns();
777            Iterator<InsertField> iter = fields1.iterator();
778            while ( iter.hasNext() ) {
779                InsertField field1 = iter.next();
780                if ( !field1.isPK() ) {
781                    InsertField field2 = row2.getColumn( field1.getColumnName() );
782                    Object value1 = field1.getValue();
783                    Object value2 = null;
784                    if ( field2 != null )
785                        value2 = field2.getValue();
786                    if ( value1 == null ) {
787                        if ( value2 == null ) {
788                            continue;
789                        }
790                        return false;
791                    }
792                    if ( !value1.equals( value2 ) ) {
793                        return false;
794                    }
795                }
796            }
797            return true;
798        }
799    
800        private void replaceInsertRow( InsertRow oldRow, InsertRow newRow ) {
801    
802            Collection<InsertField> oldFields = oldRow.getColumns();
803            for ( InsertField field : oldFields ) {
804                InsertField toField = field.getReferencedField();
805                if ( toField != null ) {
806                    LOG.logDebug( "Removing reference to field '" + toField + "'" );
807                    toField.removeReferencingField( field );
808                }
809            }
810    
811            Collection<InsertField> referencingFields = oldRow.getReferencingFields();
812            for ( InsertField fromField : referencingFields ) {
813                LOG.logDebug( "Replacing reference for field '" + fromField + "'" );
814                InsertField field = newRow.getColumn( fromField.getReferencedField().getColumnName() );
815                LOG.logDebug( "" + field );
816                fromField.relinkField( field );
817            }
818        }
819    }