001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/io/datastore/sql/transaction/UpdateHandler.java $ 002 /*---------------------------------------------------------------------------- 003 This file is part of deegree, http://deegree.org/ 004 Copyright (C) 2001-2009 by: 005 Department of Geography, University of Bonn 006 and 007 lat/lon GmbH 008 009 This library is free software; you can redistribute it and/or modify it under 010 the terms of the GNU Lesser General Public License as published by the Free 011 Software Foundation; either version 2.1 of the License, or (at your option) 012 any later version. 013 This library is distributed in the hope that it will be useful, but WITHOUT 014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 016 details. 017 You should have received a copy of the GNU Lesser General Public License 018 along with this library; if not, write to the Free Software Foundation, Inc., 019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 020 021 Contact information: 022 023 lat/lon GmbH 024 Aennchenstr. 19, 53177 Bonn 025 Germany 026 http://lat-lon.de/ 027 028 Department of Geography, University of Bonn 029 Prof. Dr. Klaus Greve 030 Postfach 1147, 53001 Bonn 031 Germany 032 http://www.geographie.uni-bonn.de/deegree/ 033 034 e-mail: info@deegree.org 035 ----------------------------------------------------------------------------*/ 036 package org.deegree.io.datastore.sql.transaction; 037 038 import java.sql.Connection; 039 import java.sql.PreparedStatement; 040 import java.sql.ResultSet; 041 import java.sql.SQLException; 042 import java.util.ArrayList; 043 import java.util.Enumeration; 044 import java.util.Hashtable; 045 import java.util.LinkedHashMap; 046 import java.util.List; 047 import java.util.Map; 048 049 import org.deegree.datatypes.QualifiedName; 050 import org.deegree.datatypes.Types; 051 import org.deegree.framework.log.ILogger; 052 import org.deegree.framework.log.LoggerFactory; 053 import org.deegree.i18n.Messages; 054 import org.deegree.io.datastore.DatastoreException; 055 import org.deegree.io.datastore.FeatureId; 056 import org.deegree.io.datastore.TransactionException; 057 import org.deegree.io.datastore.idgenerator.FeatureIdAssigner; 058 import org.deegree.io.datastore.schema.MappedFeaturePropertyType; 059 import org.deegree.io.datastore.schema.MappedFeatureType; 060 import org.deegree.io.datastore.schema.MappedGeometryPropertyType; 061 import org.deegree.io.datastore.schema.MappedPropertyType; 062 import org.deegree.io.datastore.schema.MappedSimplePropertyType; 063 import org.deegree.io.datastore.schema.TableRelation; 064 import org.deegree.io.datastore.schema.TableRelation.FK_INFO; 065 import org.deegree.io.datastore.schema.content.MappingField; 066 import org.deegree.io.datastore.schema.content.MappingGeometryField; 067 import org.deegree.io.datastore.schema.content.SimpleContent; 068 import org.deegree.io.datastore.sql.AbstractRequestHandler; 069 import org.deegree.io.datastore.sql.StatementBuffer; 070 import org.deegree.io.datastore.sql.TableAliasGenerator; 071 import org.deegree.io.datastore.sql.transaction.delete.DeleteHandler; 072 import org.deegree.io.datastore.sql.transaction.insert.InsertHandler; 073 import org.deegree.model.feature.Feature; 074 import org.deegree.model.feature.FeatureProperty; 075 import org.deegree.model.feature.schema.FeaturePropertyType; 076 import org.deegree.model.feature.schema.PropertyType; 077 import org.deegree.model.filterencoding.Filter; 078 import org.deegree.model.spatialschema.Geometry; 079 import org.deegree.ogcbase.ElementStep; 080 import org.deegree.ogcbase.PropertyPath; 081 import org.deegree.ogcbase.PropertyPathStep; 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: rbezema $ 091 * 092 * @version $Revision: 19436 $, $Date: 2009-08-31 16:37:44 +0200 (Mo, 31 Aug 2009) $ 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, String lockId ) { 112 super( dsTa.getDatastore(), aliasGenerator, conn ); 113 this.dsTa = dsTa; 114 this.lockId = lockId; 115 } 116 117 /** 118 * Performs an update operation against the associated datastore. 119 * 120 * @param ft 121 * @param replacementProps 122 * @param filter 123 * @return number of updated (root) feature instances 124 * @throws DatastoreException 125 */ 126 public int performUpdate( MappedFeatureType ft, Map<PropertyPath, FeatureProperty> replacementProps, Filter filter ) 127 throws DatastoreException { 128 129 List<FeatureId> fids = determineAffectedAndModifiableFIDs( ft, filter, this.lockId ); 130 131 LOG.logDebug( "Updating: " + ft ); 132 for ( FeatureId fid : fids ) { 133 Hashtable<String, Hashtable<FeatureId, StatementBuffer>> tableToFeatureUpdate = new Hashtable<String, Hashtable<FeatureId, StatementBuffer>>(); 134 135 for ( PropertyPath property : replacementProps.keySet() ) { 136 LOG.logDebug( "Updating feature: " + fid ); 137 FeatureProperty propertyValue = replacementProps.get( property ); 138 performUpdate( fid, ft, property, propertyValue, tableToFeatureUpdate ); 139 } 140 141 for ( Hashtable<FeatureId, StatementBuffer> featureStatementBuffers : tableToFeatureUpdate.values() ) { 142 for ( Enumeration<FeatureId> it = featureStatementBuffers.keys(); it.hasMoreElements(); ) { 143 FeatureId statementFid = it.nextElement(); 144 StatementBuffer query = featureStatementBuffers.get( statementFid ); 145 146 query.append( " WHERE " ); 147 appendFIDWhereCondition( query, statementFid ); 148 149 PreparedStatement stmt = null; 150 LOG.logDebug( "Performing aggregate update of in-table properties: " + query.getQueryString() ); 151 try { 152 stmt = this.datastore.prepareStatement( conn, query ); 153 stmt.execute(); 154 } catch ( SQLException e ) { 155 throw new DatastoreException( "Error in performUpdate(): " + e.getMessage() ); 156 } finally { 157 if ( stmt != null ) { 158 try { 159 stmt.close(); 160 } catch ( SQLException e ) { 161 LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e ); 162 } 163 } 164 } 165 } 166 } 167 } 168 return fids.size(); 169 } 170 171 /** 172 * Performs an update operation (replace-style) against the associated datastore. 173 * <p> 174 * All features matched by the given filter are altered, so their properties are identical to those of the specified 175 * replacement feature. 176 * </p> 177 * <p> 178 * NOTE: Currently, the contained feature must not contain any feature-valued properties or multi-properties. 179 * </p> 180 * 181 * @param mappedFeatureType 182 * @param replacementFeature 183 * @param filter 184 * @return number of updated (root) feature instances 185 * @throws DatastoreException 186 */ 187 public int performUpdate( MappedFeatureType mappedFeatureType, Feature replacementFeature, Filter filter ) 188 throws DatastoreException { 189 LOG.logDebug( "Updating (replace): " + mappedFeatureType ); 190 if ( filter != null ) { 191 LOG.logDebug( " filter: " + filter.to110XML() ); 192 } 193 194 PropertyType[] replPropertyTypes = replacementFeature.getFeatureType().getProperties(); 195 int result = 0; 196 // rb: updating complex features is a needed feature, gml:id reservation as well, therefore let's see if we are 197 // updating a complex feature, we do the old school, delete->insert (loosing gml:id) if updating a simple 198 // feature, do a 'new school' update. 199 if ( replPropertyTypes != null ) { 200 boolean oldSchool = false; 201 for ( int i = 0; i < replPropertyTypes.length && !oldSchool; ++i ) { 202 PropertyType rpt = replPropertyTypes[i]; 203 oldSchool = rpt != null 204 && ( rpt instanceof FeaturePropertyType || rpt instanceof MappedFeaturePropertyType ); 205 206 } 207 if ( oldSchool ) { 208 LOG.logWarning( "The given featuretype, is a complex feature type, updating this feature will result in the loss of gml:id's in the update feature and it's references." ); 209 result = performUpdateWithFeatures( mappedFeatureType, replacementFeature, filter ); 210 } else { 211 LOG.logDebug( "Updating feature with correct gml:id handling (replace): " + mappedFeatureType ); 212 result = performUpdateCorrectForProperties( mappedFeatureType, replacementFeature, filter ); 213 } 214 } 215 return result; 216 } 217 218 /** 219 * Performs an update operation (replace-style) against the associated datastore. 220 * <p> 221 * All features matched by the given filter are altered, so their properties are identical to those of the specified 222 * replacement feature. 223 * </p> 224 * <p> 225 * NOTE: Currently, the contained feature must not contain any feature-valued properties or multi-properties. 226 * </p> 227 * 228 * @param mappedFeatureType 229 * @param replacementFeature 230 * @param filter 231 * @return number of updated (root) feature instances 232 * @throws DatastoreException 233 */ 234 private int performUpdateCorrectForProperties( MappedFeatureType mappedFeatureType, Feature replacementFeature, 235 Filter filter ) 236 throws DatastoreException { 237 238 Map<PropertyPath, FeatureProperty> replaceProperties = new LinkedHashMap<PropertyPath, FeatureProperty>(); 239 FeatureProperty[] featureProps = replacementFeature.getProperties(); 240 for ( FeatureProperty featureProperty : featureProps ) { 241 List<PropertyPathStep> steps = new ArrayList<PropertyPathStep>( 1 ); 242 steps.add( new ElementStep( featureProperty.getName() ) ); 243 PropertyPath path = new PropertyPath( steps ); 244 replaceProperties.put( path, featureProperty ); 245 } 246 return performUpdate( mappedFeatureType, replaceProperties, filter ); 247 } 248 249 /** 250 * Performs an update operation against the associated datastore. 251 * <p> 252 * The filter must match exactly one feature instance (or none) which is then replaced by the specified replacement 253 * feature. 254 * 255 * @param mappedFeatureType 256 * @param replacementFeature 257 * @param filter 258 * @return number of updated (root) feature instances (0 or 1) 259 * @throws DatastoreException 260 */ 261 private int performUpdateWithFeatures( MappedFeatureType mappedFeatureType, Feature replacementFeature, 262 Filter filter ) 263 throws DatastoreException { 264 265 List<FeatureId> fids = determineAffectedAndModifiableFIDs( mappedFeatureType, filter, this.lockId ); 266 267 if ( fids.size() > 1 ) { 268 String msg = Messages.getMessage( "DATASTORE_MORE_THAN_ONE_FEATURE" ); 269 throw new DatastoreException( msg ); 270 } 271 DeleteHandler deleteHandler = new DeleteHandler( this.dsTa, this.aliasGenerator, this.conn, this.lockId ); 272 deleteHandler.performDelete( mappedFeatureType, filter ); 273 274 // identify stored subfeatures / assign feature ids 275 FeatureIdAssigner fidAssigner = new FeatureIdAssigner( Insert.ID_GEN.GENERATE_NEW ); 276 fidAssigner.assignFID( replacementFeature, this.dsTa ); 277 // TODO remove this hack 278 fidAssigner.markStoredFeatures(); 279 280 InsertHandler insertHandler = new InsertHandler( this.dsTa, this.aliasGenerator, this.conn ); 281 List<Feature> features = new ArrayList<Feature>(); 282 features.add( replacementFeature ); 283 insertHandler.performInsert( features ); 284 285 return fids.size(); 286 } 287 288 /** 289 * Performs the update (replacing of a property) of the given feature instance. 290 * <p> 291 * If the selected property is a direct property of the feature, the root feature is updated, otherwise the targeted 292 * subfeatures have to be determined first. 293 * 294 * @param fid 295 * @param ft 296 * @param propertyName 297 * @param replacementProperty 298 * @param statementBuffers 299 * @throws DatastoreException 300 */ 301 private void performUpdate( FeatureId fid, MappedFeatureType ft, PropertyPath propertyName, 302 FeatureProperty replacementProperty, 303 Hashtable<String, Hashtable<FeatureId, StatementBuffer>> statementBuffers ) 304 throws DatastoreException { 305 306 Object replacementValue = replacementProperty.getValue(); 307 LOG.logDebug( "Updating fid: " + fid + ", propertyName: " + propertyName + " -> " + replacementValue ); 308 309 int steps = propertyName.getSteps(); 310 QualifiedName propName = propertyName.getStep( steps - 1 ).getPropertyName(); 311 if ( steps > 2 ) { 312 QualifiedName subFtName = propertyName.getStep( steps - 2 ).getPropertyName(); 313 MappedFeatureType subFt = this.datastore.getFeatureType( subFtName ); 314 MappedPropertyType pt = (MappedPropertyType) subFt.getProperty( propName ); 315 List<TableRelation> tablePath = getTablePath( ft, propertyName ); 316 List<FeatureId> subFids = determineAffectedFIDs( fid, subFt, tablePath ); 317 for ( FeatureId subFid : subFids ) { 318 updateProperty( subFid, subFt, pt, replacementValue, statementBuffers ); 319 } 320 } else { 321 MappedPropertyType pt = (MappedPropertyType) ft.getProperty( propName ); 322 updateProperty( fid, ft, pt, replacementValue, statementBuffers ); 323 } 324 } 325 326 /** 327 * Determines the subfeature instances that are targeted by the given PropertyName. 328 * 329 * @param fid 330 * @param subFt 331 * @param path 332 * @return the matched feature ids 333 * @throws DatastoreException 334 */ 335 private List<FeatureId> determineAffectedFIDs( FeatureId fid, MappedFeatureType subFt, List<TableRelation> path ) 336 throws DatastoreException { 337 338 List<FeatureId> subFids = new ArrayList<FeatureId>(); 339 340 this.aliasGenerator.reset(); 341 String[] tableAliases = this.aliasGenerator.generateUniqueAliases( path.size() + 1 ); 342 String toTableAlias = tableAliases[tableAliases.length - 1]; 343 StatementBuffer query = new StatementBuffer(); 344 query.append( "SELECT " ); 345 appendFeatureIdColumns( subFt, toTableAlias, query ); 346 query.append( " FROM " ); 347 query.append( path.get( 0 ).getFromTable() ); 348 query.append( " " ); 349 query.append( tableAliases[0] ); 350 // append joins 351 for ( int i = 0; i < path.size(); i++ ) { 352 query.append( " JOIN " ); 353 query.append( path.get( i ).getToTable() ); 354 query.append( " " ); 355 query.append( tableAliases[i + 1] ); 356 query.append( " ON " ); 357 MappingField[] fromFields = path.get( i ).getFromFields(); 358 MappingField[] toFields = path.get( i ).getToFields(); 359 for ( int j = 0; j < fromFields.length; j++ ) { 360 query.append( tableAliases[i] ); 361 query.append( '.' ); 362 query.append( fromFields[j].getField() ); 363 query.append( '=' ); 364 query.append( tableAliases[i + 1] ); 365 query.append( '.' ); 366 query.append( toFields[j].getField() ); 367 } 368 } 369 query.append( " WHERE " ); 370 MappingField[] fidFields = fid.getFidDefinition().getIdFields(); 371 for ( int i = 0; i < fidFields.length; i++ ) { 372 query.append( tableAliases[0] ); 373 query.append( '.' ); 374 query.append( fidFields[i].getField() ); 375 query.append( "=?" ); 376 query.addArgument( fid.getValue( i ), fidFields[i].getType() ); 377 if ( i != fidFields.length - 1 ) { 378 query.append( " AND " ); 379 } 380 } 381 382 PreparedStatement stmt = null; 383 ResultSet rs = null; 384 try { 385 stmt = this.datastore.prepareStatement( conn, query ); 386 rs = stmt.executeQuery(); 387 subFids = extractFeatureIds( rs, subFt ); 388 } catch ( SQLException e ) { 389 throw new DatastoreException( "Error in determineAffectedFIDs(): " + e.getMessage() ); 390 } finally { 391 try { 392 if ( rs != null ) { 393 try { 394 rs.close(); 395 } catch ( SQLException e ) { 396 LOG.logError( "Error closing result set: '" + e.getMessage() + "'.", e ); 397 } 398 } 399 } finally { 400 if ( stmt != null ) { 401 try { 402 stmt.close(); 403 } catch ( SQLException e ) { 404 LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e ); 405 } 406 } 407 } 408 } 409 return subFids; 410 } 411 412 /** 413 * Returns the relations (the "path") that lead from the feature type's table to the subfeature table which is 414 * targeted by the specified property name. 415 * 416 * @param ft 417 * source feature type 418 * @param path 419 * property name 420 * @return relations that lead from the feature type's table to the subfeature table 421 */ 422 private List<TableRelation> getTablePath( MappedFeatureType ft, PropertyPath path ) { 423 List<TableRelation> relations = new ArrayList<TableRelation>(); 424 for ( int i = 1; i < path.getSteps() - 2; i += 2 ) { 425 QualifiedName propName = path.getStep( i ).getPropertyName(); 426 MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft.getProperty( propName ); 427 TableRelation[] tableRelations = pt.getTableRelations(); 428 for ( int j = 0; j < tableRelations.length; j++ ) { 429 relations.add( tableRelations[j] ); 430 } 431 ft = pt.getFeatureTypeReference().getFeatureType(); 432 } 433 return relations; 434 } 435 436 /** 437 * Replaces the specified feature's property with the given value. 438 * 439 * @param fid 440 * @param ft 441 * @param pt 442 * @param replacementValue 443 * @throws DatastoreException 444 */ 445 private void updateProperty( FeatureId fid, MappedFeatureType ft, MappedPropertyType pt, Object replacementValue, 446 Hashtable<String, Hashtable<FeatureId, StatementBuffer>> statementBuffers ) 447 throws DatastoreException { 448 449 LOG.logDebug( "Updating property '" + pt.getName() + "' of feature '" + fid + "'." ); 450 if ( !ft.isUpdatable() ) { 451 String msg = Messages.getMessage( "DATASTORE_FT_NOT_UPDATABLE", ft.getName() ); 452 throw new DatastoreException( msg ); 453 } 454 TableRelation[] tablePath = pt.getTableRelations(); 455 if ( pt instanceof MappedSimplePropertyType ) { 456 SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent(); 457 if ( content.isUpdateable() ) { 458 if ( content instanceof MappingField ) { 459 updateSimpleProperty( fid, tablePath, (MappingField) content, replacementValue, statementBuffers ); 460 } 461 } else { 462 LOG.logInfo( "Ignoring property '" + pt.getName() + "' in update - content is virtual." ); 463 } 464 } else if ( pt instanceof MappedGeometryPropertyType ) { 465 MappedGeometryPropertyType geomPt = (MappedGeometryPropertyType) pt; 466 MappingGeometryField dbField = geomPt.getMappingField(); 467 Geometry deegreeGeometry = (Geometry) replacementValue; 468 Object dbGeometry; 469 470 int createSrsCode = dbField.getSRS(); 471 int targetSrsCode = -1; 472 473 if ( deegreeGeometry.getCoordinateSystem() == null ) { 474 LOG.logDebug( "No SRS information for geometry available. Assuming '" + geomPt.getSRS() + "'." ); 475 } else if ( !geomPt.getSRS().toString().equals( deegreeGeometry.getCoordinateSystem().getIdentifier() ) ) { 476 String msg = "Insert-Transformation: geometry srs: " 477 + deegreeGeometry.getCoordinateSystem().getIdentifier() + " -> property srs: " 478 + geomPt.getSRS(); 479 LOG.logDebug( msg ); 480 if ( createSrsCode == -1 ) { 481 msg = Messages.getMessage( "DATASTORE_SRS_NOT_SPECIFIED", pt.getName(), 482 deegreeGeometry.getCoordinateSystem(), geomPt.getSRS() ); 483 throw new TransactionException( msg ); 484 } 485 try { 486 createSrsCode = datastore.getNativeSRSCode( deegreeGeometry.getCoordinateSystem().getIdentifier() ); 487 } catch ( DatastoreException e ) { 488 throw new TransactionException( e.getMessage(), e ); 489 } 490 targetSrsCode = dbField.getSRS(); 491 } 492 493 try { 494 dbGeometry = this.datastore.convertDeegreeToDBGeometry( deegreeGeometry, createSrsCode, this.conn ); 495 } catch ( DatastoreException e ) { 496 throw new TransactionException( e.getMessage(), e ); 497 } 498 499 // TODO remove this Oracle hack 500 if ( this.datastore.getClass().getName().contains( "OracleDatastore" ) ) { 501 dbField = new MappingGeometryField( dbField.getTable(), dbField.getField(), Types.STRUCT, 502 dbField.getSRS() ); 503 } 504 505 updateGeometryProperty( fid, tablePath, dbField, dbGeometry, targetSrsCode, statementBuffers ); 506 } else if ( pt instanceof FeaturePropertyType ) { 507 updateProperty( fid, ft, (MappedFeaturePropertyType) pt, (Feature) replacementValue ); 508 } else { 509 throw new DatastoreException( "Internal error: Properties with type '" + pt.getClass() 510 + "' are not handled in UpdateHandler." ); 511 } 512 } 513 514 /** 515 * Updates a simple property of the specified feature. 516 * <p> 517 * Three cases are distinguished (which all have to be handled differently): 518 * <ol> 519 * <li>property value stored in feature table</li> 520 * <li>property value stored in property table, fk in property table</li> 521 * <li>property value stored in property table, fk in feature table</li> 522 * </ol> 523 * 524 * @param fid 525 * @param tablePath 526 * @param dbField 527 * @param replacementValue 528 * @throws DatastoreException 529 */ 530 private void updateSimpleProperty( FeatureId fid, TableRelation[] tablePath, MappingField dbField, 531 Object replacementValue, 532 Hashtable<String, Hashtable<FeatureId, StatementBuffer>> statementBuffers ) 533 throws DatastoreException { 534 535 if ( tablePath.length == 0 ) { 536 updateSimpleProperty( fid, dbField, replacementValue, statementBuffers ); 537 } else if ( tablePath.length == 1 ) { 538 TableRelation relation = tablePath[0]; 539 if ( tablePath[0].getFKInfo() == FK_INFO.fkIsToField ) { 540 Object[] keyValues = determineKeyValues( fid, relation ); 541 if ( keyValues != null ) { 542 deletePropertyRows( relation, keyValues ); 543 } 544 if ( replacementValue != null ) { 545 insertPropertyRow( relation, keyValues, dbField, replacementValue ); 546 } 547 } else { 548 Object[] oldKeyValues = determineKeyValues( fid, relation ); 549 Object[] newKeyValues = findOrInsertPropertyRow( relation, dbField, replacementValue ); 550 updateFeatureRow( fid, relation, newKeyValues ); 551 if ( oldKeyValues != null ) { 552 deleteOrphanedPropertyRows( relation, oldKeyValues ); 553 } 554 } 555 } else { 556 throw new DatastoreException( "Updating of properties that are stored in " 557 + "related tables using join tables is not " + "supported." ); 558 } 559 } 560 561 /** 562 * Updates a geometry property of the specified feature. 563 * <p> 564 * Three cases are distinguished (which all have to be handled differently): 565 * <ol> 566 * <li>property value stored in feature table</li> 567 * <li>property value stored in property table, fk in property table</li> 568 * <li>property value stored in property table, fk in feature table</li> 569 * </ol> 570 * 571 * @param fid 572 * @param tablePath 573 * @param dbField 574 * @param replacementValue 575 * @param targetSrsCode 576 * @throws DatastoreException 577 */ 578 private void updateGeometryProperty( FeatureId fid, TableRelation[] tablePath, MappingGeometryField dbField, 579 Object replacementValue, int targetSrsCode, 580 Hashtable<String, Hashtable<FeatureId, StatementBuffer>> statementBuffers ) 581 throws DatastoreException { 582 583 if ( tablePath.length == 0 ) { 584 updateGeometryProperty( fid, dbField, replacementValue, targetSrsCode, statementBuffers ); 585 } else if ( tablePath.length == 1 ) { 586 throw new DatastoreException( "Updating of geometry properties that are stored in " 587 + "related tables is not supported." ); 588 } else { 589 throw new DatastoreException( "Updating of properties that are stored in " 590 + "related tables using join tables is not " + "supported." ); 591 } 592 } 593 594 private void updateFeatureRow( FeatureId fid, TableRelation relation, Object[] newKeyValues ) 595 throws DatastoreException { 596 597 StatementBuffer query = new StatementBuffer(); 598 query.append( "UPDATE " ); 599 query.append( relation.getFromTable() ); 600 query.append( " SET " ); 601 MappingField[] fromFields = relation.getFromFields(); 602 for ( int i = 0; i < newKeyValues.length; i++ ) { 603 query.append( fromFields[i].getField() ); 604 query.append( "=?" ); 605 query.addArgument( newKeyValues[i], fromFields[i].getType() ); 606 } 607 query.append( " WHERE " ); 608 appendFIDWhereCondition( query, fid ); 609 610 LOG.logDebug( "Performing update: " + query.getQueryString() ); 611 612 PreparedStatement stmt = null; 613 try { 614 stmt = this.datastore.prepareStatement( conn, query ); 615 stmt.execute(); 616 } catch ( SQLException e ) { 617 throw new DatastoreException( "Error in performUpdate(): " + e.getMessage() ); 618 } finally { 619 if ( stmt != null ) { 620 try { 621 stmt.close(); 622 } catch ( SQLException e ) { 623 LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e ); 624 } 625 } 626 } 627 } 628 629 /** 630 * Updates a simple property of the specified feature. 631 * <p> 632 * This method handles the case where the property is stored in the feature table itself, so a single UPDATE 633 * statement is sufficient. 634 * 635 * If there is already another UPDATE statement created for this feature and table than a column=value fragment is 636 * added to this statement. 637 * 638 * @param fid 639 * @param dbField 640 * @param replacementValue 641 * @throws DatastoreException 642 */ 643 private void updateSimpleProperty( FeatureId fid, MappingField dbField, Object replacementValue, 644 Hashtable<String, Hashtable<FeatureId, StatementBuffer>> statementBuffers ) { 645 646 Hashtable<FeatureId, StatementBuffer> featureStatementBuffers = statementBuffers.get( dbField.getTable() ); 647 StatementBuffer query = null; 648 if ( featureStatementBuffers == null ) { 649 featureStatementBuffers = new Hashtable<FeatureId, StatementBuffer>(); 650 statementBuffers.put( dbField.getTable(), featureStatementBuffers ); 651 } else { 652 query = featureStatementBuffers.get( fid ); 653 } 654 655 if ( query == null ) { 656 query = new StatementBuffer(); 657 query.append( "UPDATE " ); 658 query.append( dbField.getTable() ); 659 query.append( " SET " ); 660 661 featureStatementBuffers.put( fid, query ); 662 } else { 663 query.append( ", " ); 664 } 665 666 query.append( dbField.getField() ); 667 query.append( "=?" ); 668 query.addArgument( replacementValue, dbField.getType() ); 669 } 670 671 /** 672 * Updates a geometry property of the specified feature. 673 * <p> 674 * This method handles the case where the property is stored in the feature table itself, so a single UPDATE 675 * statement is sufficient. 676 * 677 * If there is already another UPDATE statement created for this feature and table than a column=value fragment is 678 * added to this statement. 679 * 680 * @param fid 681 * @param dbField 682 * @param replacementValue 683 * @param targetSrsCode 684 * @throws DatastoreException 685 */ 686 private void updateGeometryProperty( FeatureId fid, MappingGeometryField dbField, Object replacementValue, 687 int targetSrsCode, 688 Hashtable<String, Hashtable<FeatureId, StatementBuffer>> statementBuffers ) 689 throws DatastoreException { 690 691 Hashtable<FeatureId, StatementBuffer> featureStatementBuffers = statementBuffers.get( dbField.getTable() ); 692 StatementBuffer query = null; 693 if ( featureStatementBuffers == null ) { 694 featureStatementBuffers = new Hashtable<FeatureId, StatementBuffer>(); 695 statementBuffers.put( dbField.getTable(), featureStatementBuffers ); 696 } else { 697 query = featureStatementBuffers.get( fid ); 698 } 699 700 if ( query == null ) { 701 query = new StatementBuffer(); 702 query.append( "UPDATE " ); 703 query.append( dbField.getTable() ); 704 query.append( " SET " ); 705 706 featureStatementBuffers.put( fid, query ); 707 } else { 708 query.append( ", " ); 709 } 710 711 query.append( dbField.getField() ); 712 query.append( "=" ); 713 String placeHolder = "?"; 714 if ( targetSrsCode != -1 ) { 715 placeHolder = this.datastore.buildSRSTransformCall( "?", targetSrsCode ); 716 } 717 query.append( placeHolder ); 718 query.addArgument( replacementValue, dbField.getType() ); 719 } 720 721 /** 722 * Determines the values for the key columns that are referenced by the given table relation (as from fields). 723 * 724 * @param fid 725 * @param relation 726 * @return the values for the key columns 727 * @throws DatastoreException 728 */ 729 private Object[] determineKeyValues( FeatureId fid, TableRelation relation ) 730 throws DatastoreException { 731 732 StatementBuffer query = new StatementBuffer(); 733 query.append( "SELECT " ); 734 MappingField[] fromFields = relation.getFromFields(); 735 for ( int i = 0; i < fromFields.length; i++ ) { 736 query.append( fromFields[i].getField() ); 737 if ( i != fromFields.length - 1 ) { 738 query.append( ',' ); 739 } 740 } 741 query.append( " FROM " ); 742 query.append( relation.getFromTable() ); 743 query.append( " WHERE " ); 744 appendFIDWhereCondition( query, fid ); 745 746 Object[] keyValues = new Object[fromFields.length]; 747 LOG.logDebug( "determineKeyValues: " + query.getQueryString() ); 748 PreparedStatement stmt = null; 749 try { 750 stmt = this.datastore.prepareStatement( conn, query ); 751 ResultSet rs = stmt.executeQuery(); 752 if ( rs.next() ) { 753 for ( int i = 0; i < keyValues.length; i++ ) { 754 Object value = rs.getObject( i + 1 ); 755 if ( value != null ) { 756 keyValues[i] = value; 757 } else { 758 keyValues = null; 759 break; 760 } 761 } 762 } else { 763 LOG.logError( "Internal error. Result is empty (no rows)." ); 764 throw new SQLException(); 765 } 766 } catch ( SQLException e ) { 767 throw new DatastoreException( "Error in performUpdate(): " + e.getMessage() ); 768 } finally { 769 if ( stmt != null ) { 770 try { 771 stmt.close(); 772 } catch ( SQLException e ) { 773 LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e ); 774 } 775 } 776 } 777 return keyValues; 778 } 779 780 private void deletePropertyRows( TableRelation relation, Object[] keyValues ) 781 throws DatastoreException { 782 783 StatementBuffer query = new StatementBuffer(); 784 query.append( "DELETE FROM " ); 785 query.append( relation.getToTable() ); 786 query.append( " WHERE " ); 787 MappingField[] toFields = relation.getToFields(); 788 for ( int i = 0; i < toFields.length; i++ ) { 789 query.append( toFields[i].getField() ); 790 query.append( "=?" ); 791 query.addArgument( keyValues[i], toFields[i].getType() ); 792 if ( i != toFields.length - 1 ) { 793 query.append( " AND " ); 794 } 795 } 796 797 PreparedStatement stmt = null; 798 LOG.logDebug( "deletePropertyRows: " + query.getQueryString() ); 799 try { 800 stmt = this.datastore.prepareStatement( conn, query ); 801 stmt.execute(); 802 } catch ( SQLException e ) { 803 throw new DatastoreException( "Error in performUpdate(): " + e.getMessage() ); 804 } finally { 805 if ( stmt != null ) { 806 try { 807 stmt.close(); 808 } catch ( SQLException e ) { 809 LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e ); 810 } 811 } 812 } 813 } 814 815 private void insertPropertyRow( TableRelation relation, Object[] keyValues, MappingField dbField, 816 Object replacementValue ) 817 throws DatastoreException { 818 819 if ( keyValues == null ) { 820 if ( relation.getFromFields().length > 1 ) { 821 throw new DatastoreException( "Key generation for compound keys is not supported." ); 822 } 823 // generate new primary key 824 keyValues = new Object[1]; 825 keyValues[0] = relation.getIdGenerator().getNewId( dsTa ); 826 } 827 828 StatementBuffer query = new StatementBuffer(); 829 query.append( "INSERT INTO " ); 830 query.append( relation.getToTable() ); 831 query.append( " (" ); 832 MappingField[] toFields = relation.getToFields(); 833 for ( int i = 0; i < toFields.length; i++ ) { 834 query.append( toFields[i].getField() ); 835 query.append( ',' ); 836 } 837 query.append( dbField.getField() ); 838 query.append( ") VALUES (" ); 839 for ( int i = 0; i < toFields.length; i++ ) { 840 query.append( '?' ); 841 query.addArgument( keyValues[i], toFields[i].getType() ); 842 query.append( ',' ); 843 } 844 query.append( "?)" ); 845 query.addArgument( replacementValue, dbField.getType() ); 846 847 PreparedStatement stmt = null; 848 LOG.logDebug( "insertPropertyRow: " + query.getQueryString() ); 849 try { 850 stmt = this.datastore.prepareStatement( conn, query ); 851 stmt.execute(); 852 } catch ( SQLException e ) { 853 throw new DatastoreException( "Error in performUpdate(): " + e.getMessage() ); 854 } finally { 855 if ( stmt != null ) { 856 try { 857 stmt.close(); 858 } catch ( SQLException e ) { 859 LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e ); 860 } 861 } 862 } 863 } 864 865 /** 866 * Returns the foreign key value(s) for the row that stores the given property. 867 * <p> 868 * If the row already exists, the existing key is returned, otherwise a new row for the property is inserted first. 869 * 870 * @param relation 871 * @param dbField 872 * @param replacementValue 873 * @return foreign key value(s) for the row that stores the given property 874 * @throws DatastoreException 875 */ 876 private Object[] findOrInsertPropertyRow( TableRelation relation, MappingField dbField, Object replacementValue ) 877 throws DatastoreException { 878 879 Object[] keyValues = null; 880 881 if ( dbField.getType() != Types.GEOMETRY ) { 882 StatementBuffer query = new StatementBuffer(); 883 query.append( "SELECT " ); 884 MappingField[] toFields = relation.getToFields(); 885 for ( int i = 0; i < toFields.length; i++ ) { 886 query.append( toFields[i].getField() ); 887 if ( i != toFields.length - 1 ) { 888 query.append( ',' ); 889 } 890 } 891 query.append( " FROM " ); 892 query.append( relation.getToTable() ); 893 query.append( " WHERE " ); 894 query.append( dbField.getField() ); 895 query.append( "=?" ); 896 query.addArgument( replacementValue, dbField.getType() ); 897 898 PreparedStatement stmt = null; 899 LOG.logDebug( "findOrInsertPropertyRow: " + query.getQueryString() ); 900 try { 901 stmt = this.datastore.prepareStatement( conn, query ); 902 ResultSet rs = stmt.executeQuery(); 903 if ( rs.next() ) { 904 keyValues = new Object[toFields.length]; 905 for ( int i = 0; i < toFields.length; i++ ) { 906 keyValues[i] = rs.getObject( i + 1 ); 907 } 908 } 909 } catch ( SQLException e ) { 910 throw new DatastoreException( "Error in findOrInsertPropertyRow(): " + e.getMessage() ); 911 } finally { 912 if ( stmt != null ) { 913 try { 914 stmt.close(); 915 } catch ( SQLException e ) { 916 LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e ); 917 } 918 } 919 } 920 if ( keyValues != null ) { 921 return keyValues; 922 } 923 } 924 925 if ( relation.getToFields().length > 1 ) { 926 throw new DatastoreException( "Key generation for compound keys is not supported." ); 927 } 928 929 // property does not yet exist (or it's a geometry) 930 keyValues = new Object[1]; 931 // generate new PK 932 keyValues[0] = relation.getNewPK( this.dsTa ); 933 insertPropertyRow( relation, keyValues, dbField, replacementValue ); 934 935 return keyValues; 936 } 937 938 private void deleteOrphanedPropertyRows( TableRelation relation, Object[] keyValues ) 939 throws DatastoreException { 940 DeleteHandler deleteHandler = new DeleteHandler( this.dsTa, this.aliasGenerator, this.conn, this.lockId ); 941 deleteHandler.deleteOrphanedPropertyRows( relation, keyValues ); 942 } 943 944 private void updateProperty( @SuppressWarnings("unused") 945 FeatureId fid, @SuppressWarnings("unused") 946 MappedFeatureType ft, @SuppressWarnings("unused") 947 MappedFeaturePropertyType pt, @SuppressWarnings("unused") 948 Feature replacementFeature ) { 949 throw new UnsupportedOperationException( "Updating of feature properties is not implemented yet." ); 950 } 951 952 private void appendFIDWhereCondition( StatementBuffer query, FeatureId fid ) { 953 MappingField[] fidFields = fid.getFidDefinition().getIdFields(); 954 for ( int i = 0; i < fidFields.length; i++ ) { 955 query.append( fidFields[i].getField() ); 956 query.append( "=?" ); 957 query.addArgument( fid.getValue( i ), fidFields[i].getType() ); 958 if ( i != fidFields.length - 1 ) { 959 query.append( " AND " ); 960 } 961 } 962 } 963 }