001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/io/datastore/sql/transaction/UpdateHandler.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2006 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstraße 19
030     53177 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041     
042     ---------------------------------------------------------------------------*/
043    package org.deegree.io.datastore.sql.transaction;
044    
045    import java.sql.Connection;
046    import java.sql.PreparedStatement;
047    import java.sql.ResultSet;
048    import java.sql.SQLException;
049    import java.util.ArrayList;
050    import java.util.List;
051    import java.util.Map;
052    
053    import org.deegree.datatypes.QualifiedName;
054    import org.deegree.datatypes.Types;
055    import org.deegree.framework.log.ILogger;
056    import org.deegree.framework.log.LoggerFactory;
057    import org.deegree.i18n.Messages;
058    import org.deegree.io.datastore.DatastoreException;
059    import org.deegree.io.datastore.FeatureId;
060    import org.deegree.io.datastore.idgenerator.FeatureIdAssigner;
061    import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
062    import org.deegree.io.datastore.schema.MappedFeatureType;
063    import org.deegree.io.datastore.schema.MappedGeometryPropertyType;
064    import org.deegree.io.datastore.schema.MappedPropertyType;
065    import org.deegree.io.datastore.schema.MappedSimplePropertyType;
066    import org.deegree.io.datastore.schema.TableRelation;
067    import org.deegree.io.datastore.schema.TableRelation.FK_INFO;
068    import org.deegree.io.datastore.schema.content.MappingField;
069    import org.deegree.io.datastore.schema.content.MappingGeometryField;
070    import org.deegree.io.datastore.schema.content.SimpleContent;
071    import org.deegree.io.datastore.sql.AbstractRequestHandler;
072    import org.deegree.io.datastore.sql.StatementBuffer;
073    import org.deegree.io.datastore.sql.TableAliasGenerator;
074    import org.deegree.io.datastore.sql.transaction.delete.DeleteHandler;
075    import org.deegree.io.datastore.sql.transaction.insert.InsertHandler;
076    import org.deegree.model.feature.Feature;
077    import org.deegree.model.feature.FeatureProperty;
078    import org.deegree.model.feature.schema.FeaturePropertyType;
079    import org.deegree.model.filterencoding.Filter;
080    import org.deegree.model.spatialschema.Geometry;
081    import org.deegree.ogcbase.PropertyPath;
082    import org.deegree.ogcwebservices.wfs.operation.transaction.Insert;
083    import org.deegree.ogcwebservices.wfs.operation.transaction.Transaction;
084    import org.deegree.ogcwebservices.wfs.operation.transaction.Update;
085    
086    /**
087     * Handler for {@link Update} operations (usually contained in {@link Transaction} requests).
088     * 
089     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
090     * @author last edited by: $Author: mschneider $
091     * 
092     * @version $Revision: 7375 $, $Date: 2007-05-30 18:28:12 +0200 (Mi, 30 Mai 2007) $
093     */
094    public class UpdateHandler extends AbstractRequestHandler {
095    
096        private static final ILogger LOG = LoggerFactory.getLogger( UpdateHandler.class );
097    
098        private SQLTransaction dsTa;
099    
100        private String lockId;
101    
102        /**
103         * Creates a new <code>UpdateHandler</code> from the given parameters.
104         * 
105         * @param dsTa
106         * @param aliasGenerator
107         * @param conn
108         * @param lockId
109         *            optional id of associated lock (may be null)
110         */
111        public UpdateHandler( SQLTransaction dsTa, TableAliasGenerator aliasGenerator, Connection conn,
112                              String lockId ) {
113            super( dsTa.getDatastore(), aliasGenerator, conn );
114            this.dsTa = dsTa;
115            this.lockId = lockId;
116        }
117    
118        /**
119         * Performs an update operation against the associated datastore.
120         * 
121         * @param ft
122         * @param replacementProps
123         * @param filter
124         * @return number of updated (root) feature instances
125         * @throws DatastoreException
126         */
127        public int performUpdate( MappedFeatureType ft, Map<PropertyPath, FeatureProperty> replacementProps,
128                                  Filter filter )
129                                throws DatastoreException {
130    
131            List<FeatureId> fids = determineAffectedAndModifiableFIDs( ft, filter, this.lockId );
132    
133            LOG.logDebug( "Updating: " + ft );
134            for ( PropertyPath property : replacementProps.keySet() ) {
135                FeatureProperty propertyValue = replacementProps.get( property );
136                for ( FeatureId fid : fids ) {
137                    LOG.logDebug( "Updating feature: " + fid );
138                    performUpdate( fid, ft, property, propertyValue );
139                }
140            }
141            return fids.size();
142        }
143    
144        /**
145         * Performs an update operation against the associated datastore.
146         * <p>
147         * The filter must match exactly one feature instance (or none) which is then replaced by the
148         * specified replacement feature.
149         * 
150         * @param mappedFeatureType
151         * @param replacementFeature
152         * @param filter
153         * @return number of updated (root) feature instances (0 or 1)
154         * @throws DatastoreException
155         */
156        public int performUpdate( MappedFeatureType mappedFeatureType, Feature replacementFeature,
157                                  Filter filter )
158                                throws DatastoreException {
159    
160            LOG.logDebug( "Updating (replace): " + mappedFeatureType );
161            if ( filter != null ) {
162                LOG.logDebug( " filter: " + filter.toXML() );
163            }
164    
165            List<FeatureId> fids = determineAffectedAndModifiableFIDs( mappedFeatureType, filter,
166                                                                       this.lockId );
167    
168            if ( fids.size() > 1 ) {
169                String msg = Messages.getMessage( "DATASTORE_MORE_THAN_ONE_FEATURE" );
170                throw new DatastoreException( msg );
171            }
172            DeleteHandler deleteHandler = new DeleteHandler( this.dsTa, this.aliasGenerator, this.conn,
173                                                             this.lockId );
174            deleteHandler.performDelete( mappedFeatureType, filter );
175    
176            // identify stored subfeatures / assign feature ids
177            FeatureIdAssigner fidAssigner = new FeatureIdAssigner( Insert.ID_GEN.GENERATE_NEW );
178            fidAssigner.assignFID( replacementFeature, this.dsTa );
179            // TODO remove this hack
180            fidAssigner.markStoredFeatures();
181    
182            InsertHandler insertHandler = new InsertHandler( this.dsTa, this.aliasGenerator, this.conn );
183            List<Feature> features = new ArrayList<Feature>();
184            features.add( replacementFeature );
185            insertHandler.performInsert( features );
186    
187            return fids.size();
188        }
189    
190        /**
191         * Performs the update (replacing of a property) of the given feature instance.
192         * <p>
193         * If the selected property is a direct property of the feature, the root feature is updated,
194         * otherwise the targeted subfeatures have to be determined first.
195         * 
196         * @param fid
197         * @param ft
198         * @param propertyName
199         * @param replacementProperty
200         * @throws DatastoreException
201         */
202        private void performUpdate( FeatureId fid, MappedFeatureType ft, PropertyPath propertyName,
203                                    FeatureProperty replacementProperty )
204                                throws DatastoreException {
205    
206            Object replacementValue = replacementProperty.getValue();
207            LOG.logDebug( "Updating fid: " + fid + ", propertyName: " + propertyName + " -> "
208                          + replacementValue );
209    
210            int steps = propertyName.getSteps();
211            QualifiedName propName = propertyName.getStep( steps - 1 ).getPropertyName();
212            if ( steps > 2 ) {
213                QualifiedName subFtName = propertyName.getStep( steps - 2 ).getPropertyName();
214                MappedFeatureType subFt = this.datastore.getFeatureType( subFtName );
215                MappedPropertyType pt = (MappedPropertyType) subFt.getProperty( propName );
216                List<TableRelation> tablePath = getTablePath( ft, propertyName );
217                List<FeatureId> subFids = determineAffectedFIDs( fid, subFt, tablePath );
218                for ( FeatureId subFid : subFids ) {
219                    updateProperty( subFid, subFt, pt, replacementValue );
220                }
221            } else {
222                MappedPropertyType pt = (MappedPropertyType) ft.getProperty( propName );
223                updateProperty( fid, ft, pt, replacementValue );
224            }
225        }
226    
227        /**
228         * Determines the subfeature instances that are targeted by the given PropertyName.
229         * 
230         * @param fid
231         * @param subFt
232         * @param propertyName
233         * @return the matched feature ids
234         * @throws DatastoreException
235         */
236        private List<FeatureId> determineAffectedFIDs( FeatureId fid, MappedFeatureType subFt,
237                                                       List<TableRelation> path )
238                                throws DatastoreException {
239    
240            List<FeatureId> subFids = new ArrayList<FeatureId>();
241    
242            this.aliasGenerator.reset();
243            String[] tableAliases = this.aliasGenerator.generateUniqueAliases( path.size() + 1 );
244            String toTableAlias = tableAliases[tableAliases.length - 1];
245            StatementBuffer query = new StatementBuffer();
246            query.append( "SELECT " );
247            appendFeatureIdColumns( subFt, toTableAlias, query );
248            query.append( " FROM " );
249            query.append( path.get( 0 ).getFromTable() );
250            query.append( " " );
251            query.append( tableAliases[0] );
252            // append joins
253            for ( int i = 0; i < path.size(); i++ ) {
254                query.append( " JOIN " );
255                query.append( path.get( i ).getToTable() );
256                query.append( " " );
257                query.append( tableAliases[i + 1] );
258                query.append( " ON " );
259                MappingField[] fromFields = path.get( i ).getFromFields();
260                MappingField[] toFields = path.get( i ).getToFields();
261                for ( int j = 0; j < fromFields.length; j++ ) {
262                    query.append( tableAliases[i] );
263                    query.append( '.' );
264                    query.append( fromFields[j].getField() );
265                    query.append( '=' );
266                    query.append( tableAliases[i + 1] );
267                    query.append( '.' );
268                    query.append( toFields[j].getField() );
269                }
270            }
271            query.append( " WHERE " );
272            MappingField[] fidFields = fid.getFidDefinition().getIdFields();
273            for ( int i = 0; i < fidFields.length; i++ ) {
274                query.append( tableAliases[0] );
275                query.append( '.' );
276                query.append( fidFields[i].getField() );
277                query.append( "=?" );
278                query.addArgument( fid.getValue( i ), fidFields[i].getType() );
279                if ( i != fidFields.length - 1 ) {
280                    query.append( " AND " );
281                }
282            }
283    
284            PreparedStatement stmt = null;
285            ResultSet rs = null;
286            try {
287                stmt = this.datastore.prepareStatement( conn, query );
288                rs = stmt.executeQuery();
289                subFids = extractFeatureIds( rs, subFt );
290            } catch ( SQLException e ) {
291                throw new DatastoreException( "Error in determineAffectedFIDs(): " + e.getMessage() );
292            } finally {
293                try {
294                    if ( rs != null ) {
295                        try {
296                            rs.close();
297                        } catch ( SQLException e ) {
298                            LOG.logError( "Error closing result set: '" + e.getMessage() + "'.", e );
299                        }
300                    }
301                } finally {
302                    if ( stmt != null ) {
303                        try {
304                            stmt.close();
305                        } catch ( SQLException e ) {
306                            LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
307                        }
308                    }
309                }
310            }
311            return subFids;
312        }
313    
314        /**
315         * Returns the relations (the "path") that lead from the feature type's table to the subfeature
316         * table which is targeted by the specified property name.
317         * 
318         * @param ft
319         *            source feature type
320         * @param path
321         *            property name
322         * @return relations that lead from the feature type's table to the subfeature table
323         */
324        private List<TableRelation> getTablePath( MappedFeatureType ft, PropertyPath path ) {
325            List<TableRelation> relations = new ArrayList<TableRelation>();
326            for ( int i = 1; i < path.getSteps() - 2; i += 2 ) {
327                QualifiedName propName = path.getStep( i ).getPropertyName();
328                MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft.getProperty( propName );
329                TableRelation[] tableRelations = pt.getTableRelations();
330                for ( int j = 0; j < tableRelations.length; j++ ) {
331                    relations.add( tableRelations[j] );
332                }
333                ft = pt.getFeatureTypeReference().getFeatureType();
334            }
335            return relations;
336        }
337    
338        /**
339         * Replaces the specified feature's property with the given value.
340         * 
341         * @param fid
342         * @param ft
343         * @param pt
344         * @param replacementValue
345         * @throws DatastoreException
346         */
347        private void updateProperty( FeatureId fid, MappedFeatureType ft, MappedPropertyType pt,
348                                     Object replacementValue )
349                                throws DatastoreException {
350            LOG.logDebug( "Updating property '" + pt.getName() + "' of feature '" + fid + "'." );
351    
352            if ( !ft.isUpdatable() ) {
353                String msg = Messages.getMessage( "DATASTORE_FT_NOT_UPDATABLE", ft.getName() );
354                throw new DatastoreException( msg );
355            }
356            TableRelation[] tablePath = pt.getTableRelations();
357            if ( pt instanceof MappedSimplePropertyType ) {
358                SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent();
359                if ( content.isUpdateable() ) {
360                    if ( content instanceof MappingField ) {
361                        updateProperty( fid, tablePath, (MappingField) content, replacementValue );
362                    }
363                } else {
364                    LOG.logInfo( "Ignoring property '" + pt.getName() + "' in update - is virtual." );
365                }
366            } else if ( pt instanceof MappedGeometryPropertyType ) {
367                MappingGeometryField dbField = ( (MappedGeometryPropertyType) pt ).getMappingField();
368                Object dbGeometry = this.datastore.convertDeegreeToDBGeometry(
369                                                                               (Geometry) replacementValue,
370                                                                               dbField.getSRS(),
371                                                                               this.conn );
372                // TODO remove this Oracle hack
373                if ( this.datastore.getClass().getName().contains( "OracleDatastore" ) ) {
374                    dbField = new MappingGeometryField( dbField.getTable(), dbField.getField(),
375                                                        Types.STRUCT, dbField.getSRS() );
376                }
377                updateProperty( fid, tablePath, dbField, dbGeometry );
378            } else if ( pt instanceof FeaturePropertyType ) {
379                updateProperty( fid, ft, (MappedFeaturePropertyType) pt, (Feature) replacementValue );
380            } else {
381                throw new DatastoreException( "Internal error: Properties with type '" + pt.getClass()
382                                              + "' are not handled in UpdateHandler." );
383            }
384        }
385    
386        /**
387         * Updates a simple / geometry property of the specified feature.
388         * <p>
389         * Three cases are distinguished (which all have to be handled differently):
390         * <ol>
391         * <li>property value stored in feature table</li>
392         * <li>property value stored in property table, fk in property table</li>
393         * <li>property value stored in property table, fk in feature table</li>
394         * </ol>
395         * 
396         * @param fid
397         * @param tablePath
398         * @param dbField
399         * @param replacementValue
400         * @throws DatastoreException
401         */
402        private void updateProperty( FeatureId fid, TableRelation[] tablePath, MappingField dbField,
403                                     Object replacementValue )
404                                throws DatastoreException {
405    
406            if ( tablePath.length == 0 ) {
407                updateProperty( fid, dbField, replacementValue );
408            } else if ( tablePath.length == 1 ) {
409                TableRelation relation = tablePath[0];
410                if ( tablePath[0].getFKInfo() == FK_INFO.fkIsToField ) {
411                    Object[] keyValues = determineKeyValues( fid, relation );
412                    if ( keyValues != null ) {
413                        deletePropertyRows( relation, keyValues );
414                    }
415                    if (replacementValue != null) {
416                        insertPropertyRow( relation, keyValues, dbField, replacementValue );
417                    }
418                } else {
419                    Object[] oldKeyValues = determineKeyValues( fid, relation );
420                    Object[] newKeyValues = findOrInsertPropertyRow( relation, dbField,
421                                                                     replacementValue );
422                    updateFeatureRow( fid, relation, newKeyValues );
423                    if ( oldKeyValues != null ) {
424                        deleteOrphanedPropertyRows( relation, oldKeyValues );
425                    }
426                }
427            } else {
428                throw new DatastoreException( "Updating of properties that are stored in "
429                                              + "related tables using join tables is not "
430                                              + "supported." );
431            }
432        }
433    
434        private void updateFeatureRow( FeatureId fid, TableRelation relation, Object[] newKeyValues )
435                                throws DatastoreException {
436    
437            StatementBuffer query = new StatementBuffer();
438            query.append( "UPDATE " );
439            query.append( relation.getFromTable() );
440            query.append( " SET " );
441            MappingField[] fromFields = relation.getFromFields();
442            for ( int i = 0; i < newKeyValues.length; i++ ) {
443                query.append( fromFields[i].getField() );
444                query.append( "=?" );
445                query.addArgument( newKeyValues[i], fromFields[i].getType() );
446            }
447            query.append( " WHERE " );
448            appendFIDWhereCondition( query, fid );
449    
450            LOG.logDebug( "Performing update: " + query.getQueryString() );
451    
452            PreparedStatement stmt = null;
453            try {
454                stmt = this.datastore.prepareStatement( conn, query );
455                stmt.execute();
456            } catch ( SQLException e ) {
457                throw new DatastoreException( "Error in performUpdate(): " + e.getMessage() );
458            } finally {
459                if ( stmt != null ) {
460                    try {
461                        stmt.close();
462                    } catch ( SQLException e ) {
463                        LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
464                    }
465                }
466            }
467    
468        }
469    
470        /**
471         * Updates a simple / geometry property of the specified feature.
472         * <p>
473         * This method handles the case where the property is stored in the feature table itself, so a
474         * single UPDATE statement is sufficient.
475         * 
476         * @param fid
477         * @param dbField
478         * @param replacementValue
479         * @throws DatastoreException
480         */
481        private void updateProperty( FeatureId fid, MappingField dbField, Object replacementValue )
482                                throws DatastoreException {
483    
484            StatementBuffer query = new StatementBuffer();
485            query.append( "UPDATE " );
486            query.append( dbField.getTable() );
487            query.append( " SET " );
488            query.append( dbField.getField() );
489            query.append( "=?" );
490            query.addArgument( replacementValue, dbField.getType() );
491            query.append( " WHERE " );
492            appendFIDWhereCondition( query, fid );
493    
494            LOG.logDebug( "Performing update: " + query.getQueryString() );
495    
496            PreparedStatement stmt = null;
497            try {
498                stmt = this.datastore.prepareStatement( conn, query );
499                stmt.execute();
500            } catch ( SQLException e ) {
501                throw new DatastoreException( "Error in performUpdate(): " + e.getMessage() );
502            } finally {
503                if ( stmt != null ) {
504                    try {
505                        stmt.close();
506                    } catch ( SQLException e ) {
507                        LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
508                    }
509                }
510            }
511        }
512    
513        /**
514         * Determines the values for the key columns that are referenced by the given table relation (as
515         * from fields).
516         * 
517         * @param fid
518         * @param relation
519         * @return the values for the key columns
520         * @throws DatastoreException
521         */
522        private Object[] determineKeyValues( FeatureId fid, TableRelation relation )
523                                throws DatastoreException {
524    
525            StatementBuffer query = new StatementBuffer();
526            query.append( "SELECT " );
527            MappingField[] fromFields = relation.getFromFields();
528            for ( int i = 0; i < fromFields.length; i++ ) {
529                query.append( fromFields[i].getField() );
530                if ( i != fromFields.length - 1 ) {
531                    query.append( ',' );
532                }
533            }
534            query.append( " FROM " );
535            query.append( relation.getFromTable() );
536            query.append( " WHERE " );
537            appendFIDWhereCondition( query, fid );
538    
539            Object[] keyValues = new Object[fromFields.length];
540            LOG.logDebug( "determineKeyValues: " + query.getQueryString() );
541            PreparedStatement stmt = null;
542            try {
543                stmt = this.datastore.prepareStatement( conn, query );
544                ResultSet rs = stmt.executeQuery();
545                if ( rs.next() ) {
546                    for ( int i = 0; i < keyValues.length; i++ ) {
547                        Object value = rs.getObject( i + 1 );
548                        if ( value != null ) {
549                            keyValues[i] = value;
550                        } else {
551                            keyValues = null;
552                            break;
553                        }
554                    }
555                } else {
556                    LOG.logError( "Internal error. Result is empty (no rows)." );
557                    throw new SQLException();
558                }
559            } catch ( SQLException e ) {
560                throw new DatastoreException( "Error in performUpdate(): " + e.getMessage() );
561            } finally {
562                if ( stmt != null ) {
563                    try {
564                        stmt.close();
565                    } catch ( SQLException e ) {
566                        LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
567                    }
568                }
569            }
570            return keyValues;
571        }
572    
573        private void deletePropertyRows( TableRelation relation, Object[] keyValues )
574                                throws DatastoreException {
575    
576            StatementBuffer query = new StatementBuffer();
577            query.append( "DELETE FROM " );
578            query.append( relation.getToTable() );
579            query.append( " WHERE " );
580            MappingField[] toFields = relation.getToFields();
581            for ( int i = 0; i < toFields.length; i++ ) {
582                query.append( toFields[i].getField() );
583                query.append( "=?" );
584                query.addArgument( keyValues[i], toFields[i].getType() );
585                if ( i != toFields.length - 1 ) {
586                    query.append( " AND " );
587                }
588            }
589    
590            PreparedStatement stmt = null;
591            LOG.logDebug( "deletePropertyRows: " + query.getQueryString() );
592            try {
593                stmt = this.datastore.prepareStatement( conn, query );
594                stmt.execute();
595            } catch ( SQLException e ) {
596                throw new DatastoreException( "Error in performUpdate(): " + e.getMessage() );
597            } finally {
598                if ( stmt != null ) {
599                    try {
600                        stmt.close();
601                    } catch ( SQLException e ) {
602                        LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
603                    }
604                }
605            }
606        }
607    
608        private void insertPropertyRow( TableRelation relation, Object[] keyValues,
609                                        MappingField dbField, Object replacementValue )
610                                throws DatastoreException {
611    
612            if ( keyValues == null ) {
613                if ( relation.getFromFields().length > 1 ) {
614                    throw new DatastoreException( "Key generation for compound keys is not supported." );
615                }
616                // generate new primary key
617                keyValues = new Object[1];
618                keyValues[0] = relation.getIdGenerator().getNewId( dsTa );
619            }
620    
621            StatementBuffer query = new StatementBuffer();
622            query.append( "INSERT INTO " );
623            query.append( relation.getToTable() );
624            query.append( " (" );
625            MappingField[] toFields = relation.getToFields();
626            for ( int i = 0; i < toFields.length; i++ ) {
627                query.append( toFields[i].getField() );
628                query.append( ',' );
629            }
630            query.append( dbField.getField() );
631            query.append( ") VALUES (" );
632            for ( int i = 0; i < toFields.length; i++ ) {
633                query.append( '?' );
634                query.addArgument( keyValues[i], toFields[i].getType() );
635                query.append( ',' );
636            }
637            query.append( "?)" );
638            query.addArgument( replacementValue, dbField.getType() );
639    
640            PreparedStatement stmt = null;
641            LOG.logDebug( "insertPropertyRow: " + query.getQueryString() );
642            try {
643                stmt = this.datastore.prepareStatement( conn, query );
644                stmt.execute();
645            } catch ( SQLException e ) {
646                throw new DatastoreException( "Error in performUpdate(): " + e.getMessage() );
647            } finally {
648                if ( stmt != null ) {
649                    try {
650                        stmt.close();
651                    } catch ( SQLException e ) {
652                        LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
653                    }
654                }
655            }
656        }
657    
658        /**
659         * Returns the foreign key value(s) for the row that stores the given property.
660         * <p>
661         * If the row already exists, the existing key is returned, otherwise a new row for the property
662         * is inserted first.
663         * 
664         * @param relation
665         * @param dbField
666         * @param replacementValue
667         * @return foreign key value(s) for the row that stores the given property
668         * @throws DatastoreException
669         */
670        private Object[] findOrInsertPropertyRow( TableRelation relation, MappingField dbField,
671                                                  Object replacementValue )
672                                throws DatastoreException {
673    
674            Object[] keyValues = null;
675    
676            if ( dbField.getType() != Types.GEOMETRY ) {
677                StatementBuffer query = new StatementBuffer();
678                query.append( "SELECT " );
679                MappingField[] toFields = relation.getToFields();
680                for ( int i = 0; i < toFields.length; i++ ) {
681                    query.append( toFields[i].getField() );
682                    if ( i != toFields.length - 1 ) {
683                        query.append( ',' );
684                    }
685                }
686                query.append( " FROM " );
687                query.append( relation.getToTable() );
688                query.append( " WHERE " );
689                query.append( dbField.getField() );
690                query.append( "=?" );
691                query.addArgument( replacementValue, dbField.getType() );
692    
693                PreparedStatement stmt = null;
694                LOG.logDebug( "findOrInsertPropertyRow: " + query.getQueryString() );
695                try {
696                    stmt = this.datastore.prepareStatement( conn, query );
697                    ResultSet rs = stmt.executeQuery();
698                    if ( rs.next() ) {
699                        keyValues = new Object[toFields.length];
700                        for ( int i = 0; i < toFields.length; i++ ) {
701                            keyValues[i] = rs.getObject( i + 1 );
702                        }
703                    }
704                } catch ( SQLException e ) {
705                    throw new DatastoreException( "Error in findOrInsertPropertyRow(): "
706                                                  + e.getMessage() );
707                } finally {
708                    if ( stmt != null ) {
709                        try {
710                            stmt.close();
711                        } catch ( SQLException e ) {
712                            LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
713                        }
714                    }
715                }
716                if ( keyValues != null ) {
717                    return keyValues;
718                }
719            }
720    
721            if ( relation.getToFields().length > 1 ) {
722                throw new DatastoreException( "Key generation for compound keys is not supported." );
723            }
724    
725            // property does not yet exist (or it's a geometry)
726            keyValues = new Object[1];
727            // generate new PK
728            keyValues[0] = relation.getNewPK( this.dsTa );
729            insertPropertyRow( relation, keyValues, dbField, replacementValue );
730    
731            return keyValues;
732        }
733    
734        private void deleteOrphanedPropertyRows( TableRelation relation, Object[] keyValues )
735                                throws DatastoreException {
736            DeleteHandler deleteHandler = new DeleteHandler( this.dsTa, this.aliasGenerator, this.conn,
737                                                             this.lockId );
738            deleteHandler.deleteOrphanedPropertyRows( relation, keyValues );
739        }
740    
741        private void updateProperty( @SuppressWarnings("unused")
742        FeatureId fid, @SuppressWarnings("unused")
743        MappedFeatureType ft, @SuppressWarnings("unused")
744        MappedFeaturePropertyType pt, @SuppressWarnings("unused")
745        Feature replacementFeature ) {
746            throw new UnsupportedOperationException(
747                                                     "Updating of feature properties is not implemented yet." );
748        }
749    
750        private void appendFIDWhereCondition( StatementBuffer query, FeatureId fid ) {
751            MappingField[] fidFields = fid.getFidDefinition().getIdFields();
752            for ( int i = 0; i < fidFields.length; i++ ) {
753                query.append( fidFields[i].getField() );
754                query.append( "=?" );
755                query.addArgument( fid.getValue( i ), fidFields[i].getType() );
756                if ( i != fidFields.length - 1 ) {
757                    query.append( " AND " );
758                }
759            }
760        }
761    }