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 }