001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/io/datastore/sql/transaction/UpdateHandler.java $
002 /*---------------- FILE HEADER ------------------------------------------
003
004 This file is part of deegree.
005 Copyright (C) 2001-2008 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: apoth $
091 *
092 * @version $Revision: 9342 $, $Date: 2007-12-27 13:32:57 +0100 (Do, 27 Dez 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 }