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